Compare commits

..

3 Commits

Author SHA1 Message Date
Margaret Culotta
69ea14c513 cleanup and move 2025-05-21 11:33:06 -05:00
Margaret Culotta
05d6f36f30 merge main and move doc section into schema 2025-05-21 11:06:42 -05:00
Margaret Culotta
02541d3f48 add findUnique docs 2025-05-21 10:02:14 -05:00
1541 changed files with 80517 additions and 64724 deletions

View File

@@ -2,22 +2,42 @@
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"fixed": [
"fixed": [],
"linked": [
[
"cojson",
"cojson-storage",
"cojson-storage-indexeddb",
"cojson-storage-sqlite",
"cojson-transport-ws",
"jazz-browser",
"jazz-auth-clerk",
"jazz-auth-betterauth",
"jazz-betterauth-client-plugin",
"jazz-betterauth-server-plugin",
"jazz-react-auth-betterauth",
"jazz-browser-media-images",
"jazz-expo",
"jazz-inspector",
"jazz-inspector-element",
"jazz-nodejs",
"jazz-react",
"jazz-react-core",
"jazz-react-auth-clerk",
"jazz-react-native-core",
"jazz-react-native",
"jazz-react-native-media-images",
"jazz-run",
"jazz-tools"
"jazz-svelte",
"jazz-tools",
"jazz-vue"
]
],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "minor"
"updateInternalDependencies": "patch",
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}

View File

@@ -1,23 +0,0 @@
# Description
<!-- Please include a summary of the change and which issue is fixed -->
<!-- Please also include relevant motivation and context -->
<!-- Include any links to documentation like RFCs if necessary -->
<!-- Add a link to to relevant preview environments or anything that would simplify visual review process -->
<!-- Supplemental screenshots and video are encouraged, but the primary description should be in text -->
## Manual testing instructions
<!-- Add any actions required to manually test the changes -->
## Tests
- [ ] Tests have been added and/or updated
- [ ] Tests have not been updated, because: <!-- Insert reason for not updating tests here -->
- [ ] I need help with writing tests
## Checklist
- [ ] I've updated the part of the docs that are affected the PR changes
- [ ] I've generated a changeset, if a version bump is required
- [ ] I've updated the jsDoc comments to the public APIs I've modified, or added them when missing

37
.github/workflows/build-examples.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build Examples
on:
push:
branches: [ "main" ]
jobs:
build-examples:
runs-on: blacksmith-4vcpu-ubuntu-2204
strategy:
matrix:
example: [
"chat",
"clerk",
"passkey",
"inspector",
"music-player",
"password-manager",
"pets",
"reactions",
"todo",
]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Source Code
uses: ./.github/actions/source-code/
- name: Pnpm Build
run: |
pnpm install
pnpm turbo build;
working-directory: ./examples/${{ matrix.example }}

26
.github/workflows/build-starters.yaml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Build Starters
on:
push:
branches: ["main"]
jobs:
build-starters:
runs-on: blacksmith-4vcpu-ubuntu-2204
strategy:
matrix:
starter: ["react-passkey-auth"]
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup Source Code
uses: ./.github/actions/source-code/
- name: Pnpm Build
run: |
pnpm install
pnpm turbo build;
working-directory: ./starters/${{ matrix.starter }}

View File

@@ -1,28 +1,19 @@
name: Code quality
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
push:
branches:
- "main"
pull_request:
jobs:
quality:
runs-on: blacksmith-2vcpu-ubuntu-2404-arm
runs-on: blacksmith-4vcpu-ubuntu-2204
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: 1.9.4
version: latest
- name: Run Biome
run: biome ci .

View File

@@ -1,11 +1,5 @@
name: End-to-End Tests for React Native
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
pull_request:
types: [opened, synchronize, reopened]
@@ -44,6 +38,7 @@ jobs:
- name: chat-rn-expo App Pre Build
working-directory: ./examples/chat-rn-expo
run: |
pnpm build
pnpm expo prebuild --clean
- name: Install Maestro
@@ -66,8 +61,7 @@ jobs:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics
disable-animations: true
working-directory: ./examples/chat-rn-expo/
# killall due to this issue: https://github.com/ReactiveCircus/android-emulator-runner/issues/385
script: ./test/e2e/run.sh && ( killall -INT crashpad_handler || true )
script: ./test/e2e/run.sh
- name: Copy Maestro Output
if: steps.e2e_test.outcome != 'success'

View File

@@ -1,11 +1,5 @@
name: Jazz Run Tests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
push:
branches: ["main"]
@@ -14,7 +8,7 @@ on:
jobs:
test:
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-4vcpu-ubuntu-2204
timeout-minutes: 5
steps:

View File

@@ -1,11 +1,5 @@
name: Playwright Tests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
push:
branches: ["main"]
@@ -15,11 +9,11 @@ on:
jobs:
test:
timeout-minutes: 60
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-4vcpu-ubuntu-2204
continue-on-error: true
strategy:
matrix:
shard: ["1/2", "2/2"]
project: ["tests/e2e", "examples/chat", "examples/clerk", "examples/betterauth", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/organization", "examples/pets", "starters/react-passkey-auth"]
steps:
- uses: actions/checkout@v4
@@ -29,129 +23,21 @@ jobs:
- name: Setup Source Code
uses: ./.github/actions/source-code/
- name: Pnpm Build
run: pnpm turbo build
working-directory: ./${{ matrix.project }}
- name: Install Playwright Browsers
run: pnpm exec playwright install
working-directory: ./${{ matrix.project }}
- name: Run Playwright tests for shard ${{ matrix.shard }}
run: |
# Parse shard information (e.g., "1/2" -> shard_num=1, total_shards=2)
IFS='/' read -r shard_num total_shards <<< "${{ matrix.shard }}"
shard_index=$((shard_num - 1)) # Convert to 0-based index
# Debug: Print parsed values
echo "Parsed shard_num: $shard_num"
echo "Parsed total_shards: $total_shards"
echo "Calculated shard_index: $shard_index"
# Define all projects to test
all_projects=(
"tests/e2e"
"examples/chat"
"examples/chat-svelte"
"examples/clerk"
"examples/betterauth"
"examples/file-share-svelte"
"examples/form"
"examples/inspector"
"examples/music-player"
"examples/organization"
"starters/react-passkey-auth"
"starters/svelte-passkey-auth"
"tests/jazz-svelte"
)
# Calculate which projects this shard should run
shard_projects=()
for i in "${!all_projects[@]}"; do
if [ $((i % total_shards)) -eq $shard_index ]; then
shard_projects+=("${all_projects[i]}")
fi
done
# Track project results
overall_exit_code=0
failed_projects=()
passed_projects=()
echo "=== Running tests for shard ${{ matrix.shard }} ==="
echo "Projects in this shard:"
printf '%s\n' "${shard_projects[@]}"
echo
# Run tests for each project
for project in "${shard_projects[@]}"; do
echo "=== Testing project: $project ==="
# Check if project directory exists
if [ ! -d "$project" ]; then
echo "❌ FAILED: Project directory $project does not exist"
failed_projects+=("$project (directory not found)")
overall_exit_code=1
continue
fi
# Check if project has package.json
if [ ! -f "$project/package.json" ]; then
echo "❌ FAILED: No package.json found in $project"
failed_projects+=("$project (no package.json)")
overall_exit_code=1
continue
fi
# Build the project
echo "🔨 Building $project..."
cd "$project"
if [ -f .env.test ]; then
cp .env.test .env
fi
if ! pnpm turbo build; then
echo "❌ BUILD FAILED: $project"
failed_projects+=("$project (build failed)")
overall_exit_code=1
cd - > /dev/null
continue
fi
# Run Playwright tests
echo "🧪 Running Playwright tests for $project..."
if ! pnpm exec playwright test; then
echo "❌ TESTS FAILED: $project"
failed_projects+=("$project (tests failed)")
overall_exit_code=1
else
echo "✅ TESTS PASSED: $project"
passed_projects+=("$project")
fi
cd - > /dev/null
echo "=== Finished testing $project ==="
echo
done
# Print summary report
echo "=========================================="
echo "📊 TEST SUMMARY FOR SHARD ${{ matrix.shard }}"
echo "=========================================="
if [ ${#passed_projects[@]} -gt 0 ]; then
echo "✅ PASSED (${#passed_projects[@]}):"
printf ' - %s\n' "${passed_projects[@]}"
echo
fi
if [ ${#failed_projects[@]} -gt 0 ]; then
echo "❌ FAILED (${#failed_projects[@]}):"
printf ' - %s\n' "${failed_projects[@]}"
echo
fi
echo "Total projects in shard: ${#shard_projects[@]}"
echo "Passed: ${#passed_projects[@]}"
echo "Failed: ${#failed_projects[@]}"
echo "=========================================="
# Exit with overall status
exit $overall_exit_code
- name: Run Playwright tests
run: pnpm exec playwright test
working-directory: ./${{ matrix.project }}
- uses: actions/upload-artifact@v4
if: failure()
with:
name: ${{ hashFiles(format('{0}/package.json', matrix.project)) }}-playwright-report
path: ./${{ matrix.project }}/playwright-report/
retention-days: 30

View File

@@ -1,11 +1,4 @@
name: Pre-Publish tagged Pull Requests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
pull_request:
types: [opened, synchronize, reopened, labeled]

View File

@@ -17,7 +17,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-4vcpu-ubuntu-2204
steps:
- name: Checkout Repo
uses: actions/checkout@v4

View File

@@ -1,11 +1,5 @@
name: Unit Tests
concurrency:
# For pushes, this lets concurrent runs happen, so each push gets a result.
# But for other events (e.g. PRs), we can cancel the previous runs.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
on:
pull_request:
types: [opened, synchronize, reopened]
@@ -15,7 +9,7 @@ on:
jobs:
unit-tests:
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: blacksmith-4vcpu-ubuntu-2204
steps:
- name: Checkout

3
.gitignore vendored
View File

@@ -20,9 +20,6 @@ __screenshots__
# Playwright
test-results
# Java
.java-version
.husky
.vscode/*

View File

@@ -63,7 +63,7 @@ You'll need Node.js 22.x installed (we're working on support for 23.x), and pnpm
4. **Build the packages**:
```bash
pnpm build:packages
pnpm build
```
5. **Run tests** to verify everything is working:

View File

@@ -11,9 +11,8 @@
"jazz-tools.json",
"**/ios/**",
"**/android/**",
"tests/jazz-svelte/src/**",
"packages/jazz-svelte/**",
"examples/*svelte*/**",
"starters/*svelte*/**",
"examples/jazz-paper-scissors/src/routeTree.gen.ts",
"homepage/homepage/**",
"**/package.json"
@@ -56,7 +55,7 @@
}
},
{
"include": ["packages/cojson/src/storage/*/**", "cojson-transport-ws/**"],
"include": ["packages/cojson-storage*/**", "cojson-transport-ws/**"],
"linter": {
"enabled": true,
"rules": {
@@ -65,7 +64,7 @@
}
},
{
"include": ["**/tests/**"],
"include": ["packages/**/src/tests/**"],
"linter": {
"rules": {
"correctness": {

View File

@@ -36,10 +36,8 @@ yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env
.env.*
!.env.example
!.env.test
.env*
!.env
# vercel
.vercel

View File

@@ -1,221 +1,5 @@
# betterauth
## 0.1.25
### Patch Changes
- Updated dependencies [048ac1d]
- jazz-tools@0.14.22
- jazz-betterauth-server-plugin@0.14.22
- jazz-inspector@0.14.22
- jazz-react@0.14.22
- jazz-react-auth-betterauth@0.14.22
- jazz-betterauth-client-plugin@0.14.22
## 0.1.24
### Patch Changes
- Updated dependencies [e7e505e]
- Updated dependencies [13b57aa]
- Updated dependencies [5662faa]
- Updated dependencies [2116a59]
- jazz-tools@0.14.21
- jazz-betterauth-server-plugin@0.14.21
- jazz-inspector@0.14.21
- jazz-react@0.14.21
- jazz-react-auth-betterauth@0.14.21
- jazz-betterauth-client-plugin@0.14.21
## 0.1.23
### Patch Changes
- Updated dependencies [6f72419]
- Updated dependencies [04b20c2]
- jazz-tools@0.14.20
- jazz-betterauth-server-plugin@0.14.20
- jazz-inspector@0.14.20
- jazz-react@0.14.20
- jazz-react-auth-betterauth@0.14.20
- jazz-betterauth-client-plugin@0.14.20
## 0.1.22
### Patch Changes
- jazz-betterauth-client-plugin@0.14.19
- jazz-betterauth-server-plugin@0.14.19
- jazz-react-auth-betterauth@0.14.19
- jazz-inspector@0.14.19
- jazz-react@0.14.19
- jazz-tools@0.14.19
## 0.1.21
### Patch Changes
- Updated dependencies [4b950bc]
- Updated dependencies [d6d9c0a]
- Updated dependencies [c559054]
- jazz-tools@0.14.18
- jazz-betterauth-server-plugin@0.14.18
- jazz-inspector@0.14.18
- jazz-react@0.14.18
- jazz-react-auth-betterauth@0.14.18
- jazz-betterauth-client-plugin@0.14.18
## 0.1.20
### Patch Changes
- Updated dependencies [e512df4]
- jazz-betterauth-server-plugin@0.14.17
- jazz-tools@0.14.17
- jazz-betterauth-client-plugin@0.14.17
- jazz-inspector@0.14.17
- jazz-react@0.14.17
- jazz-react-auth-betterauth@0.14.17
## 0.1.19
### Patch Changes
- jazz-betterauth-server-plugin@0.14.16
- jazz-inspector@0.14.16
- jazz-react@0.14.16
- jazz-react-auth-betterauth@0.14.16
- jazz-tools@0.14.16
- jazz-betterauth-client-plugin@0.14.16
## 0.1.18
### Patch Changes
- Updated dependencies [f9590f9]
- jazz-react@0.14.15
- jazz-betterauth-server-plugin@0.14.15
- jazz-inspector@0.14.15
- jazz-react-auth-betterauth@0.14.15
- jazz-tools@0.14.15
- jazz-betterauth-client-plugin@0.14.15
## 0.1.17
### Patch Changes
- Updated dependencies [e32a1f7]
- jazz-tools@0.14.14
- jazz-betterauth-server-plugin@0.14.14
- jazz-inspector@0.14.14
- jazz-react@0.14.14
- jazz-react-auth-betterauth@0.14.14
- jazz-betterauth-client-plugin@0.14.14
## 0.1.16
### Patch Changes
- jazz-inspector@0.14.13
- jazz-react@0.14.13
- jazz-react-auth-betterauth@0.14.13
## 0.1.15
### Patch Changes
- jazz-inspector@0.14.12
- jazz-react@0.14.12
- jazz-react-auth-betterauth@0.14.12
## 0.1.14
### Patch Changes
- Updated dependencies [dc746a2]
- Updated dependencies [f869d9a]
- Updated dependencies [3fe6832]
- jazz-react-auth-betterauth@0.14.10
- jazz-inspector@0.14.10
- jazz-react@0.14.10
- jazz-tools@0.14.10
- jazz-betterauth-server-plugin@0.14.10
- jazz-betterauth-client-plugin@0.14.10
## 0.1.13
### Patch Changes
- Updated dependencies [22c2600]
- jazz-tools@0.14.9
- jazz-betterauth-server-plugin@0.14.9
- jazz-inspector@0.14.9
- jazz-react@0.14.9
- jazz-react-auth-betterauth@0.14.9
- jazz-betterauth-client-plugin@0.14.9
## 0.1.12
### Patch Changes
- Updated dependencies [637ae13]
- jazz-tools@0.14.8
- jazz-betterauth-server-plugin@0.14.8
- jazz-inspector@0.14.8
- jazz-react@0.14.8
- jazz-react-auth-betterauth@0.14.8
- jazz-betterauth-client-plugin@0.14.8
## 0.1.11
### Patch Changes
- Updated dependencies [365b0ea]
- jazz-tools@0.14.7
- jazz-betterauth-server-plugin@0.14.7
- jazz-inspector@0.14.7
- jazz-react@0.14.7
- jazz-react-auth-betterauth@0.14.7
- jazz-betterauth-client-plugin@0.14.7
## 0.1.10
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-betterauth-server-plugin@0.14.6
- jazz-inspector@0.14.6
- jazz-react@0.14.6
- jazz-react-auth-betterauth@0.14.6
- jazz-betterauth-client-plugin@0.14.6
## 0.1.9
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-betterauth-server-plugin@0.14.5
- jazz-inspector@0.14.5
- jazz-react@0.14.5
- jazz-react-auth-betterauth@0.14.5
- jazz-betterauth-client-plugin@0.14.5
## 0.1.8
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-betterauth-server-plugin@0.14.4
- jazz-inspector@0.14.4
- jazz-react@0.14.4
- jazz-react-auth-betterauth@0.14.4
- jazz-betterauth-client-plugin@0.14.4
## 0.1.7
### Patch Changes

View File

@@ -5,12 +5,10 @@ This example demonstrates how to integrate [Better Auth](https://www.better-auth
## Getting started
To run this example, you may either:
- Clone the Jazz monorepo and run this example from within.
- Create a new Jazz project using this example as a template, and run that new project.
* Clone the Jazz monorepo and run this example from within.
* Create a new Jazz project using this example as a template, and run that new project.
### Setting environment variables
- `NEXT_PUBLIC_AUTH_BASE_URL`: A URL to a Better Auth server. If undefined, the example will self-host a Better Auth server.
- `BETTER_AUTH_SECRET`: The encryption secret used by the self-hosted Better Auth server (required only if `NEXT_PUBLIC_AUTH_BASE_URL` is undefined)
- `GITHUB_CLIENT_ID`: The client ID for the GitHub OAuth provider used by the self-hosted Better Auth server (required only if `NEXT_PUBLIC_AUTH_BASE_URL` is undefined)
@@ -19,64 +17,38 @@ To run this example, you may either:
### Using this example as a template
1. Create a new Jazz project, and use this example as a template.
```sh
npx create-jazz-app@latest betterauth-app --example betterauth
```
2. Navigate to the new project and install dependencies.
2. Navigate to the new project and start the development server.
```sh
cd betterauth-app
pnpm install
```
3. Create a .env file (don't forget to set your [BETTER_AUTH_SECRET](https://www.better-auth.com/docs/installation#set-environment-variables)!)
```sh
mv .env.example .env
```
4. Start the development server
```sh
pnpm dev
```
https://www.better-auth.com/docs/installation#set-environment-variables
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/betterauth/
```
Create a .env file (don't forget to set your [BETTER_AUTH_SECRET](https://www.better-auth.com/docs/installation#set-environment-variables)!)
```sh
mv .env.example .env
```
Start the dev server.
```bash
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -1,5 +1,6 @@
{
"name": "betterauth",
"version": "0.1.7",
"private": true,
"type": "module",
"scripts": {
@@ -21,9 +22,11 @@
"better-sqlite3": "^11.9.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jazz-react-auth-betterauth": "workspace:*",
"jazz-betterauth-client-plugin": "workspace:*",
"jazz-betterauth-server-plugin": "workspace:*",
"jazz-inspector": "workspace:*",
"jazz-react": "workspace:*",
"jazz-react-auth-betterauth": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.510.0",
"next": "15.3.2",

View File

@@ -1,8 +1,8 @@
"use client";
import { Button } from "@/components/ui/button";
import { useAccount } from "jazz-react";
import { Account } from "jazz-tools";
import { useAccount } from "jazz-tools/react";
import {
AppWindowMacIcon,
FileTextIcon,

View File

@@ -1,21 +1,21 @@
"use client";
import { JazzProvider } from "jazz-react";
import { AuthProvider } from "jazz-react-auth-betterauth";
import { JazzReactProvider } from "jazz-tools/react";
import { type ReactNode, lazy } from "react";
const JazzDevTools =
process.env.NODE_ENV === "production"
? () => null
: lazy(() =>
import("jazz-tools/inspector").then((res) => ({
import("jazz-inspector").then((res) => ({
default: res.JazzInspector,
})),
);
export function JazzAndAuth({ children }: { children: ReactNode }) {
return (
<JazzReactProvider
<JazzProvider
sync={{
peer: "wss://cloud.jazz.tools/?key=betterauth-example@garden.co",
}}
@@ -28,6 +28,6 @@ export function JazzAndAuth({ children }: { children: ReactNode }) {
{children}
</AuthProvider>
<JazzDevTools />
</JazzReactProvider>
</JazzProvider>
);
}

View File

@@ -1,9 +1,9 @@
"use client";
import { Button } from "@/components/ui/button";
import { useAccount, useIsAuthenticated } from "jazz-react";
import { useAuth } from "jazz-react-auth-betterauth";
import { Account } from "jazz-tools";
import { useAccount, useIsAuthenticated } from "jazz-tools/react";
import Image from "next/image";
import Link from "next/link";
import { useCallback } from "react";

View File

@@ -1,8 +1,8 @@
"use client";
import { Button } from "@/components/ui/button";
import { useAccount, useIsAuthenticated } from "jazz-react";
import { useAuth } from "jazz-react-auth-betterauth";
import { useAccount, useIsAuthenticated } from "jazz-tools/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { toast } from "sonner";

17
examples/chat-rn-expo-clerk/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store
ios
android

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
# 🎷 Jazz + Expo + `expo-router` + Clerk Auth
## 🚀 How to Run
### 1. Inside the Workspace Root
First, install dependencies and build the project:
```bash
pnpm i
pnpm run build
```
### 2. Inside the `examples/chat-rn-expo-clerk` Directory
Next, navigate to the specific example project and run the following commands:
```bash
pnpm expo prebuild
pnpx pod-install
pnpm expo run:ios
```
This will set up and launch the app on iOS. For Android, you can replace the last command with `pnpm expo run:android`.

View File

@@ -0,0 +1,45 @@
{
"expo": {
"name": "jazz-chat-rn-expo-clerk",
"scheme": "jazz-chat-rn-expo-clerk",
"slug": "jazz-chat-rn-expo-clerk",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.jazz.chatrnclerk"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.jazz.chatrnclerk"
},
"newArchEnabled": true,
"plugins": [
"expo-secure-store",
"expo-font",
"expo-router",
"expo-sqlite",
[
"expo-image-picker",
{
"photosPermission": "The app accesses your photos to let you share them with your friends."
}
]
],
"extra": {
"eas": {
"projectId": "ca3d46e5-a10a-47ec-9d77-3b841e1c62d4"
}
}
}
}

View File

@@ -0,0 +1,15 @@
import { Redirect, Stack } from "expo-router";
import { useIsAuthenticated } from "jazz-expo";
import React from "react";
export default function HomeLayout() {
const isAuthenticated = useIsAuthenticated();
if (isAuthenticated) {
return <Redirect href={"/chat"} />;
}
return (
<Stack screenOptions={{ headerShown: false, headerBackVisible: true }} />
);
}

View File

@@ -0,0 +1,33 @@
import { SignedOut } from "@clerk/clerk-expo";
import { Link } from "expo-router";
import React from "react";
import { Text, View } from "react-native";
export default function HomePage() {
return (
<View className="flex-1 justify-center items-center bg-gray-100 p-6">
<SignedOut>
<View className="bg-white p-6 rounded-lg shadow-lg w-11/12 max-w-md">
<Text className="text-2xl font-bold text-center text-gray-900 mb-4">
Jazz 🤝 Clerk 🤝 Expo
</Text>
<Link href="/sign-in" className="mb-4">
<Text className="text-center text-blue-600 underline text-lg">
Sign In
</Text>
</Link>
<Link href="/sign-in-oauth" className="mb-4">
<Text className="text-center text-blue-600 underline text-lg">
Sign In OAuth
</Text>
</Link>
<Link href="/sign-up">
<Text className="text-center text-blue-600 underline text-lg">
Sign Up
</Text>
</Link>
</View>
</SignedOut>
</View>
);
}

View File

@@ -0,0 +1,20 @@
import { Redirect, Stack } from "expo-router";
import { useIsAuthenticated } from "jazz-expo";
export default function UnAuthenticatedLayout() {
const isAuthenticated = useIsAuthenticated();
if (isAuthenticated) {
return <Redirect href={"/chat"} />;
}
return (
<Stack
screenOptions={{
headerShown: true,
headerBackVisible: true,
headerTitle: "",
}}
/>
);
}

View File

@@ -0,0 +1,65 @@
import { useOAuth } from "@clerk/clerk-expo";
import * as Linking from "expo-linking";
import { Link } from "expo-router";
import * as WebBrowser from "expo-web-browser";
import React from "react";
import { Text, TouchableOpacity, View } from "react-native";
export const useWarmUpBrowser = () => {
React.useEffect(() => {
// Warm up the android browser to improve UX
// https://docs.expo.dev/guides/authentication/#improving-user-experience
void WebBrowser.warmUpAsync();
return () => {
void WebBrowser.coolDownAsync();
};
}, []);
};
WebBrowser.maybeCompleteAuthSession();
const SignInWithOAuth = () => {
useWarmUpBrowser();
const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" });
const onPress = React.useCallback(async () => {
try {
const { createdSessionId, signIn, signUp, setActive } =
await startOAuthFlow({
redirectUrl: Linking.createURL("/", {
scheme: "jazz-chat-rn-expo-clerk",
}),
});
if (createdSessionId) {
setActive!({ session: createdSessionId });
} else {
// Use signIn or signUp for next steps such as MFA
}
} catch (err) {
console.error("OAuth error", err);
}
}, []);
return (
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg items-center">
<TouchableOpacity
onPress={onPress}
className="w-full bg-red-500 py-3 rounded-lg flex items-center justify-center"
>
<Text className="text-white text-lg font-semibold">
Sign in with Google
</Text>
</TouchableOpacity>
<Link href="/" className="mt-4">
<Text className="text-blue-600 text-lg font-semibold underline mb-6">
Back to Home
</Text>
</Link>
</View>
</View>
);
};
export default SignInWithOAuth;

View File

@@ -0,0 +1,79 @@
import { useSignIn } from "@clerk/clerk-expo";
import { Link } from "expo-router";
import React from "react";
import { Text, TextInput, TouchableOpacity, View } from "react-native";
export default function SignInPage() {
const { signIn, setActive, isLoaded } = useSignIn();
const [emailAddress, setEmailAddress] = React.useState("");
const [password, setPassword] = React.useState("");
const [errorMessage, setErrorMessage] = React.useState("");
const onSignInPress = React.useCallback(async () => {
if (!isLoaded) {
return;
}
setErrorMessage("");
try {
const signInAttempt = await signIn.create({
identifier: emailAddress,
password,
});
if (signInAttempt.status === "complete") {
await setActive({ session: signInAttempt.createdSessionId });
} else {
console.error(JSON.stringify(signInAttempt, null, 2));
setErrorMessage("Invalid credentials. Please try again.");
}
} catch (err: any) {
console.error(JSON.stringify(err, null, 2));
if (err.errors && err.errors[0]?.message) {
setErrorMessage(err.errors[0].message);
} else {
setErrorMessage("An unexpected error occurred. Please try again.");
}
}
}, [isLoaded, emailAddress, password]);
return (
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-md">
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
Sign In
</Text>
{errorMessage ? (
<Text className="text-red-500 text-center mb-4">{errorMessage}</Text>
) : null}
<TextInput
autoCapitalize="none"
value={emailAddress}
placeholder="Email..."
onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
/>
<TextInput
value={password}
placeholder="Password..."
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
/>
<TouchableOpacity
onPress={onSignInPress}
className="w-full h-12 bg-blue-600 rounded-lg flex items-center justify-center"
>
<Text className="text-white text-lg font-semibold">Sign In</Text>
</TouchableOpacity>
<View className="flex-row items-center justify-center mt-4">
<Text className="text-gray-600">Don't have an account?</Text>
<Link href="/sign-up">
<Text className="text-blue-500 ml-2 font-semibold">Sign up</Text>
</Link>
</View>
</View>
</View>
);
}

View File

@@ -0,0 +1,120 @@
import { useSignUp } from "@clerk/clerk-expo";
import { useNavigation } from "@react-navigation/native";
import * as React from "react";
import { Text, TextInput, TouchableOpacity, View } from "react-native";
export default function SignUpPage() {
const { isLoaded, signUp, setActive } = useSignUp();
const [emailAddress, setEmailAddress] = React.useState("");
const [password, setPassword] = React.useState("");
const [pendingVerification, setPendingVerification] = React.useState(false);
const [code, setCode] = React.useState("");
const [errorMessage, setErrorMessage] = React.useState("");
const navigation = useNavigation();
const onSignUpPress = async () => {
if (!isLoaded) return;
setErrorMessage("");
try {
await signUp.create({
emailAddress,
password,
});
await signUp.prepareEmailAddressVerification({
strategy: "email_code",
});
setPendingVerification(true);
} catch (err: any) {
console.error(JSON.stringify(err, null, 2));
if (err.errors && err.errors[0]?.message) {
setErrorMessage(err.errors[0].message);
} else {
setErrorMessage("An unexpected error occurred. Please try again.");
}
}
};
const onPressVerify = async () => {
if (!isLoaded) return;
setErrorMessage("");
try {
const completeSignUp = await signUp.attemptEmailAddressVerification({
code,
});
if (completeSignUp.status === "complete") {
await setActive({ session: completeSignUp.createdSessionId });
} else {
console.error(JSON.stringify(completeSignUp, null, 2));
setErrorMessage("Failed to verify. Please check your code.");
}
} catch (err: any) {
console.error(JSON.stringify(err, null, 2));
setErrorMessage("Invalid verification code. Please try again.");
}
};
return (
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg">
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
{pendingVerification ? "Verify Email" : "Sign Up"}
</Text>
{errorMessage ? (
<Text className="text-red-500 text-center mb-4">{errorMessage}</Text>
) : null}
{!pendingVerification && (
<>
<TextInput
autoCapitalize="none"
value={emailAddress}
placeholder="Email..."
onChangeText={(email) => setEmailAddress(email)}
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
/>
<TextInput
value={password}
placeholder="Password..."
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
/>
<TouchableOpacity
onPress={onSignUpPress}
className="w-full h-12 bg-blue-600 rounded-lg flex justify-center items-center mb-4"
>
<Text className="text-white text-lg font-semibold">Sign Up</Text>
</TouchableOpacity>
</>
)}
{pendingVerification && (
<>
<TextInput
value={code}
placeholder="Verification Code..."
onChangeText={(code) => setCode(code)}
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
/>
<TouchableOpacity
onPress={onPressVerify}
className="w-full h-12 bg-green-600 rounded-lg flex justify-center items-center mb-4"
>
<Text className="text-white text-lg font-semibold">
Verify Email
</Text>
</TouchableOpacity>
</>
)}
</View>
</View>
);
}

View File

@@ -0,0 +1,42 @@
import { ScrollViewStyleReset } from "expo-router/html";
import { type PropsWithChildren } from "react";
/**
* This file is web-only and used to configure the root HTML for every web page during static rendering.
* The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs.
*/
export default function Root({ children }: PropsWithChildren) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
);
}
const responsiveBackground = `
body {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}`;

View File

@@ -0,0 +1,29 @@
import { Link, Stack } from "expo-router";
import { StyleSheet, Text, View } from "react-native";
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: "Oops!" }} />
<View style={styles.container}>
<Text>This screen doesn't exist.</Text>
<Link href="/" style={styles.link}>
<Text>Go to home screen!</Text>
</Link>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
padding: 20,
},
link: {
marginTop: 15,
paddingVertical: 15,
},
});

View File

@@ -0,0 +1,71 @@
import "../global.css";
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
import { secureStore } from "@clerk/clerk-expo/secure-store";
import { useFonts } from "expo-font";
import { Slot, useRouter, useSegments } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useIsAuthenticated, useJazzContext } from "jazz-expo";
import React, { useEffect } from "react";
import { tokenCache } from "../cache";
import { JazzAndAuth } from "../src/auth-context";
SplashScreen.preventAutoHideAsync();
function InitialLayout() {
const isAuthenticated = useIsAuthenticated();
const segments = useSegments();
const router = useRouter();
useEffect(() => {
const inAuthGroup = segments[0] === "(auth)";
if (isAuthenticated && inAuthGroup) {
router.replace("/chat");
} else if (!isAuthenticated && !inAuthGroup) {
router.replace("/");
}
SplashScreen.hideAsync();
}, [isAuthenticated, segments, router]);
return <Slot />;
}
export default function RootLayout() {
const [fontsLoaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
if (!publishableKey) {
throw new Error(
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
);
}
useEffect(() => {
if (fontsLoaded) {
} else {
SplashScreen.preventAutoHideAsync();
}
}, [fontsLoaded]);
if (!fontsLoaded) {
return null;
}
return (
<ClerkProvider
tokenCache={tokenCache}
publishableKey={publishableKey}
__experimental_resourceCache={secureStore}
>
<ClerkLoaded>
<JazzAndAuth>
<InitialLayout />
</JazzAndAuth>
</ClerkLoaded>
</ClerkProvider>
);
}

View File

@@ -0,0 +1,234 @@
import { Chat, Message } from "@/src/schema";
import { useNavigation } from "@react-navigation/native";
import clsx from "clsx";
import * as Clipboard from "expo-clipboard";
import * as ImagePicker from "expo-image-picker";
import { useLocalSearchParams } from "expo-router";
import { useAccount, useCoState } from "jazz-expo";
import { ProgressiveImg } from "jazz-expo";
import { createImage } from "jazz-react-native-media-images";
import { CoPlainText, Group, Loaded } from "jazz-tools";
import { useEffect, useLayoutEffect, useState } from "react";
import React, {
SafeAreaView,
View,
Text,
Alert,
TouchableOpacity,
FlatList,
KeyboardAvoidingView,
TextInput,
Button,
Image,
ActivityIndicator,
} from "react-native";
export default function Conversation() {
const { chatId } = useLocalSearchParams();
const { me } = useAccount();
const [chat, setChat] = useState<Loaded<typeof Chat>>();
const [message, setMessage] = useState("");
const loadedChat = useCoState(Chat, chat?.id, { resolve: { $each: true } });
const navigation = useNavigation();
const [isUploading, setIsUploading] = useState(false);
useEffect(() => {
if (chat) return;
if (chatId === "new") {
createChat();
} else {
loadChat(chatId as string);
}
}, [chat]);
// Effect to dynamically set header options
useLayoutEffect(() => {
navigation.setOptions({
headerTitle: "Chat",
headerRight: () =>
chat ? (
<Button
onPress={() => {
if (chat?.id) {
Clipboard.setStringAsync(
`https://chat.jazz.tools/#/chat/${chat.id}`,
);
Alert.alert("Copied to clipboard", `Chat ID: ${chat.id}`);
}
}}
title="Share"
/>
) : null,
});
}, [navigation, chat]);
const createChat = () => {
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
const chat = Chat.create([], { owner: group });
setChat(chat);
};
const loadChat = async (chatId: string) => {
try {
const chat = await Chat.load(chatId);
if (chat) setChat(chat);
} catch (error) {
console.log("Error loading chat", error);
Alert.alert("Error", `Error loading chat: ${error}`);
}
};
const sendMessage = () => {
if (!chat) return;
if (message.trim()) {
chat.push(
Message.create(
{ text: CoPlainText.create(message, chat._owner) },
chat._owner,
),
);
setMessage("");
}
};
const handleImageUpload = async () => {
try {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
base64: true,
quality: 0.7,
});
if (!result.canceled && result.assets[0].base64 && chat) {
setIsUploading(true);
const base64Uri = `data:image/jpeg;base64,${result.assets[0].base64}`;
const image = await createImage(base64Uri, {
owner: chat._owner,
maxSize: 2048,
});
chat.push(
Message.create(
{ text: CoPlainText.create("", chat._owner), image },
chat._owner,
),
);
}
} catch (error) {
Alert.alert("Error", "Failed to upload image");
} finally {
setIsUploading(false);
}
};
const renderMessageItem = ({ item }: { item: Loaded<typeof Message> }) => {
const isMe = item._edits.text?.by?.isMe;
return (
<View
className={clsx(
`rounded-xl px-3 py-2 max-w-[75%] my-1`,
isMe ? `bg-blue-500 self-end` : `bg-gray-200 self-start`,
)}
>
{!isMe ? (
<Text
className={clsx(
`text-xs text-gray-500 mb-1`,
isMe ? "text-right" : "text-left",
)}
>
{item._edits.text?.by?.profile?.name}
</Text>
) : null}
<View
className={clsx(
"flex relative items-end justify-between",
isMe ? "flex-row" : "flex-row",
)}
>
{item.image && (
<ProgressiveImg image={item.image} maxWidth={1024}>
{({ src, res, originalSize }) => (
<Image
source={{ uri: src }}
className="w-48 h-48 rounded-lg mb-2"
resizeMode="cover"
/>
)}
</ProgressiveImg>
)}
{item.text && (
<Text
className={clsx(
!isMe ? "text-black" : "text-gray-200",
`text-md max-w-[85%]`,
)}
>
{item.text}
</Text>
)}
<Text
className={clsx(
"text-[10px] text-right ml-2",
!isMe ? "mt-2 text-gray-500" : "mt-1 text-gray-200",
)}
>
{item._edits.text?.madeAt?.getHours().toString().padStart(2, "0")}:
{item._edits.text?.madeAt?.getMinutes().toString().padStart(2, "0")}
</Text>
</View>
</View>
);
};
return (
<View className="flex-1 bg-gray-50">
<FlatList
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 10,
paddingHorizontal: 8,
}}
className="flex"
data={loadedChat}
keyExtractor={(item) => item.id}
renderItem={renderMessageItem}
/>
<KeyboardAvoidingView
keyboardVerticalOffset={110}
behavior="padding"
className="p-3 bg-white border-t border-gray-300"
>
<SafeAreaView className="flex-row items-center gap-2">
<TouchableOpacity
onPress={handleImageUpload}
disabled={isUploading}
className="h-10 w-10 items-center justify-center"
>
{isUploading ? (
<ActivityIndicator size="small" color="#0000ff" />
) : (
<Text className="text-2xl">🖼</Text>
)}
</TouchableOpacity>
<TextInput
className="flex-1 rounded-full h-10 px-4 bg-gray-100 border border-gray-300 focus:border-blue-500 focus:bg-white"
value={message}
onChangeText={setMessage}
placeholder="Type a message..."
textAlignVertical="center"
onSubmitEditing={sendMessage}
/>
<TouchableOpacity
onPress={sendMessage}
className="bg-blue-500 rounded-full h-10 w-10 items-center justify-center"
>
<Text className="text-white text-xl"></Text>
</TouchableOpacity>
</SafeAreaView>
</KeyboardAvoidingView>
</View>
);
}

View File

@@ -0,0 +1,14 @@
import { Stack } from "expo-router";
import React from "react";
export default function ChatLayout() {
return (
<Stack
screenOptions={{
headerShown: true,
headerBackVisible: true,
headerTitle: "",
}}
/>
);
}

View File

@@ -0,0 +1,90 @@
import { useNavigation } from "@react-navigation/native";
import { useRouter } from "expo-router";
import { ID } from "jazz-tools";
import { useLayoutEffect } from "react";
import React, {
Button,
Text,
TouchableOpacity,
View,
Alert,
} from "react-native";
import { useUser } from "@clerk/clerk-expo";
import { useAccount } from "jazz-expo";
import { Chat } from "../../src/schema";
export default function ChatScreen() {
const { logOut } = useAccount();
const router = useRouter();
const navigation = useNavigation();
const { user } = useUser();
function handleLogOut() {
logOut();
router.navigate("/");
}
useLayoutEffect(() => {
navigation.setOptions({
headerTitle: "Chat",
headerRight: () => <Button onPress={handleLogOut} title="Logout" />,
});
}, [navigation]);
const loadChat = async (chatId: string | "new") => {
router.navigate(`/chat/${chatId}`);
};
const joinChat = () => {
Alert.prompt(
"Join Chat",
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
[
{
text: "Cancel",
style: "cancel",
},
{
text: "Join",
onPress: (chatId) => {
if (chatId) {
loadChat(chatId);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
},
},
],
"plain-text",
);
};
return (
<View className="flex-1 bg-gray-50">
<View className="flex-1 justify-center items-center px-6">
<View className="w-full max-w-sm bg-white p-8 rounded-lg shadow-lg">
<Text className="text-xl font-semibold text-gray-800">
Welcome, {user?.emailAddresses[0].emailAddress}
</Text>
<TouchableOpacity
onPress={() => loadChat("new")}
className="w-full bg-blue-600 py-4 rounded-md mb-4 mt-4"
>
<Text className="text-white text-lg font-semibold text-center">
Start New Chat
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={joinChat}
className="w-full bg-green-500 py-4 rounded-md"
>
<Text className="text-white text-lg font-semibold text-center">
Join Chat
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,9 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};

View File

@@ -0,0 +1,39 @@
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
export interface TokenCache {
getToken: (key: string) => Promise<string | undefined | null>;
saveToken: (key: string, token: string) => Promise<void>;
clearToken: (key: string) => void;
}
const createTokenCache = (): TokenCache => {
return {
getToken: async (key: string) => {
try {
const item = await SecureStore.getItemAsync(key);
if (item) {
console.log(`${key} was used 🔐 \n`);
} else {
console.log("No values stored under key: " + key);
}
return item;
} catch (error) {
console.error("secure store get item error: ", error);
await SecureStore.deleteItemAsync(key);
return null;
}
},
saveToken: (key: string, token: string) => {
return SecureStore.setItemAsync(key, token);
},
clearToken: (key: string) => {
return SecureStore.deleteItemAsync(key);
},
};
};
// SecureStore is not supported on the web
// https://github.com/expo/expo/issues/7744#issuecomment-611093485
export const tokenCache =
Platform.OS !== "web" ? createTokenCache() : undefined;

View File

@@ -0,0 +1,27 @@
{
"cli": {
"version": ">= 12.5.1",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"ios-simulator": {
"extends": "development",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

View File

@@ -0,0 +1,2 @@
import "./polyfills";
import "expo-router/entry";

View File

@@ -0,0 +1,35 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const { FileStore } = require("metro-cache");
const path = require("path");
// eslint-disable-next-line no-undef
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot);
// Since we are using pnpm, we have to setup the monorepo manually for Metro
// #1 - Watch all files in the monorepo
config.watchFolders = [workspaceRoot];
// #2 - Try resolving with project modules first, then workspace modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.requireCycleIgnorePatterns = [
/(^|\/|\\)node_modules($|\/|\\)/,
/(^|\/|\\)packages($|\/|\\)/,
];
// Use turborepo to restore the cache when possible
config.cacheStores = [
new FileStore({
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
}),
];
// module.exports = config;
module.exports = withNativeWind(config, { input: "./global.css" });

View File

@@ -0,0 +1 @@
/// <reference types="nativewind/types" />

View File

@@ -0,0 +1,67 @@
{
"name": "chat-rn-expo-clerk",
"main": "index.js",
"version": "1.0.126",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"android": "expo run:android",
"ios": "expo prebuild && pnpx pod-install && expo run:ios",
"web": "expo start --web",
"run:ios": "pnpm expo prebuild && npx pod-install && pnpm expo run:ios"
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bacons/text-decoder": "0.0.0",
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@clerk/clerk-expo": "^2.2.21",
"@craftzdog/react-native-buffer": "6.0.5",
"@expo/vector-icons": "^14.1.0",
"@react-native-community/netinfo": "11.4.1",
"@react-navigation/native": "7.0.19",
"@react-navigation/native-stack": "7.2.1",
"clsx": "^2.0.0",
"expo": "^53.0.8",
"expo-build-properties": "~0.14.6",
"expo-clipboard": "~7.1.4",
"expo-constants": "~17.1.6",
"expo-crypto": "~14.1.4",
"expo-dev-client": "~5.1.8",
"expo-file-system": "^18.1.9",
"expo-font": "~13.3.1",
"expo-image-picker": "~16.1.4",
"expo-linking": "~7.1.4",
"expo-router": "~5.0.6",
"expo-secure-store": "~14.2.3",
"expo-splash-screen": "~0.30.8",
"expo-sqlite": "15.2.9",
"expo-status-bar": "~2.2.3",
"expo-web-browser": "~14.1.6",
"jazz-expo": "workspace:*",
"jazz-react-native-media-images": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^4.1.21",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.2",
"react-native-gesture-handler": "~2.24.0",
"react-native-get-random-values": "^1.11.0",
"react-native-reanimated": "~3.17.5",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "4.10.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-web": "~0.20.0",
"readable-stream": "4.7.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.14",
"@types/react-test-renderer": "^19.0.0",
"react-test-renderer": "18.3.1",
"tailwindcss": "^3.4.17",
"typescript": "5.8.3"
},
"private": true
}

View File

@@ -3,6 +3,9 @@
// @ts-expect-error - @types/react-native doesn't cover this file
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
import { Buffer } from "@craftzdog/react-native-buffer";
polyfillGlobal("Buffer", () => Buffer);
// @ts-expect-error - @types/readable-stream doesn't have ReadableStream type
import { ReadableStream } from "readable-stream";
polyfillGlobal("ReadableStream", () => ReadableStream);

View File

@@ -0,0 +1 @@
export const apiKey = "chat-rn-expo-clerk-example-jazz@garden.co";

View File

@@ -0,0 +1,19 @@
import { useClerk } from "@clerk/clerk-expo";
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk";
import React, { PropsWithChildren } from "react";
import { apiKey } from "./apiKey";
export function JazzAndAuth({ children }: PropsWithChildren) {
const clerk = useClerk();
return (
<JazzProviderWithClerk
clerk={clerk}
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
>
{children}
</JazzProviderWithClerk>
);
}

View File

@@ -1,8 +1,8 @@
import { co, z } from 'jazz-tools';
import { CoList, co, coField, z } from "jazz-tools";
export const Message = co.map({
text: co.plainText(),
image: z.optional(co.image())
image: z.optional(co.image()),
});
export const Chat = co.list(Message);

View File

@@ -0,0 +1,14 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,11 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler",
"paths": {
"@/*": ["./*"]
}
},
"include": ["**/*.ts", "**/*.tsx", "nativewind-env.d.ts"]
}

View File

@@ -7,10 +7,8 @@ node_modules/
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
@@ -35,10 +33,6 @@ yarn-error.*
# typescript
*.tsbuildinfo
android/
ios/
# env files
.env
.env.*
!.env.example
!.env.test
ios
android

View File

@@ -1,107 +1,943 @@
# chat-rn-expo
## 1.0.113
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-expo@0.14.2
## 1.0.112
### Patch Changes
- Updated dependencies [cdfc105]
- jazz-tools@0.14.1
- jazz-expo@0.14.1
## 1.0.111
### Patch Changes
- Updated dependencies [5835ed1]
- jazz-tools@0.14.0
- jazz-expo@0.14.0
## 1.0.110
### Patch Changes
- jazz-expo@0.13.32
## 1.0.109
### Patch Changes
- Updated dependencies [e5b170f]
- jazz-tools@0.13.31
- jazz-expo@0.13.31
## 1.0.108
### Patch Changes
- jazz-expo@0.13.30
- jazz-tools@0.13.30
## 1.0.107
### Patch Changes
- jazz-expo@0.13.29
- jazz-tools@0.13.29
## 1.0.106
### Patch Changes
- jazz-expo@0.13.28
- jazz-tools@0.13.28
## 1.0.105
### Patch Changes
- jazz-expo@0.13.27
- jazz-tools@0.13.27
## 1.0.104
### Patch Changes
- Updated dependencies [ff846d9]
- jazz-tools@0.13.26
- jazz-expo@0.13.26
## 1.0.103
### Patch Changes
- jazz-expo@0.13.25
- jazz-tools@0.13.25
## 1.0.102
### Patch Changes
- Updated dependencies [02a240c]
- jazz-tools@0.13.23
- jazz-expo@0.13.23
## 1.0.101
### Patch Changes
- jazz-expo@0.13.22
## 1.0.100
### Patch Changes
- jazz-expo@0.13.21
- jazz-tools@0.13.21
## 1.0.99
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-expo@0.13.20
## 1.0.98
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-expo@0.13.19
## 1.0.97
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-expo@0.13.18
## 1.0.96
### Patch Changes
- jazz-expo@0.13.17
- jazz-tools@0.13.17
## 1.0.95
### Patch Changes
- jazz-expo@0.13.16
- jazz-tools@0.13.16
## 1.0.94
### Patch Changes
- jazz-expo@0.13.15
- jazz-tools@0.13.15
## 1.0.93
### Patch Changes
- Updated dependencies [bd94012]
- jazz-expo@0.13.14
- jazz-tools@0.13.14
## 1.0.92
### Patch Changes
- jazz-expo@0.13.13
- jazz-tools@0.13.13
## 1.0.91
### Patch Changes
- Updated dependencies [4547525]
- jazz-tools@0.13.12
- jazz-expo@0.13.12
## 1.0.90
### Patch Changes
- Updated dependencies [17273a6]
- jazz-tools@0.13.11
- jazz-expo@0.13.11
## 1.0.89
### Patch Changes
- jazz-expo@0.13.10
- jazz-tools@0.13.10
## 1.0.88
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-expo@0.13.9
## 1.0.87
### Patch Changes
- jazz-expo@0.13.8
## 1.0.86
### Patch Changes
- Updated dependencies [bc3d7bb]
- jazz-tools@0.13.7
- jazz-expo@0.13.7
## 1.0.85
### Patch Changes
- Updated dependencies [fe6f561]
- jazz-tools@0.13.5
- jazz-expo@0.13.5
## 1.0.84
### Patch Changes
- Updated dependencies [3129982]
- Updated dependencies [3129982]
- jazz-expo@0.13.4
- jazz-tools@0.13.4
## 1.0.83
### Patch Changes
- Updated dependencies [12f8bfa]
- Updated dependencies [bd57177]
- jazz-tools@0.13.3
- jazz-expo@0.13.3
## 1.0.82
### Patch Changes
- jazz-expo@0.13.2
- jazz-tools@0.13.2
## 1.0.81
### Patch Changes
- Updated dependencies [63a7aa0]
- jazz-expo@0.13.1
## 1.0.80
### Patch Changes
- Updated dependencies [bce3bcc]
- Updated dependencies [afd1374]
- jazz-expo@0.13.0
- jazz-tools@0.13.0
## 1.0.88
### Patch Changes
- jazz-react-native@0.12.2
- jazz-tools@0.12.2
## 1.0.87
### Patch Changes
- jazz-react-native@0.12.1
- jazz-tools@0.12.1
## 1.0.86
### Patch Changes
- Updated dependencies [01523dc]
- Updated dependencies [4ea87dc]
- Updated dependencies [1e6da19]
- Updated dependencies [b6c6a0a]
- jazz-tools@0.12.0
- jazz-react-native@0.12.0
## 1.0.85
### Patch Changes
- jazz-react-native@0.11.8
- jazz-tools@0.11.8
## 1.0.84
### Patch Changes
- Updated dependencies [a140f55]
- Updated dependencies [2b0d1b0]
- jazz-tools@0.11.7
- jazz-react-native@0.11.7
## 1.0.83
### Patch Changes
- Updated dependencies [e7c85b7]
- jazz-react-native@0.11.6
- jazz-tools@0.11.6
## 1.0.82
### Patch Changes
- jazz-react-native@0.11.5
- jazz-tools@0.11.5
## 1.0.81
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-react-native@0.11.4
## 1.0.80
### Patch Changes
- jazz-react-native@0.11.3
- jazz-tools@0.11.3
## 1.0.79
### Patch Changes
- Updated dependencies [6892dc6]
- jazz-tools@0.11.2
- jazz-react-native@0.11.2
## 1.0.78
### Patch Changes
- jazz-react-native@0.11.1
## 1.0.77
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react-native@0.11.0
## 1.0.76
### Patch Changes
- Updated dependencies [2f99de0]
- jazz-tools@0.10.15
- jazz-react-native@0.10.15
## 1.0.75
### Patch Changes
- Updated dependencies [75211e3]
- jazz-tools@0.10.14
- jazz-react-native@0.10.14
## 1.0.74
### Patch Changes
- Updated dependencies [07feedd]
- jazz-tools@0.10.13
- jazz-react-native@0.10.13
## 1.0.73
### Patch Changes
- Updated dependencies [4612e05]
- jazz-tools@0.10.12
- jazz-react-native@0.10.12
## 1.0.72
### Patch Changes
- Updated dependencies [5a54e4a]
- jazz-react-native@0.10.11
## 1.0.71
### Patch Changes
- Updated dependencies [3405d8f]
- jazz-react-native@0.10.10
## 1.0.70
### Patch Changes
- Updated dependencies [2fb6428]
- jazz-tools@0.10.8
- jazz-react-native@0.10.8
## 1.0.69
### Patch Changes
- Updated dependencies [1136d9b]
- Updated dependencies [0eed228]
- jazz-react-native@0.10.7
- jazz-tools@0.10.7
## 1.0.68
### Patch Changes
- Updated dependencies [ada802b]
- jazz-tools@0.10.6
- jazz-react-native@0.10.6
## 1.0.67
### Patch Changes
- Updated dependencies [59ff77e]
- jazz-tools@0.10.5
- jazz-react-native@0.10.5
## 1.0.66
### Patch Changes
- jazz-react-native@0.10.4
- jazz-tools@0.10.4
## 1.0.65
### Patch Changes
- Updated dependencies [d8582fc]
- jazz-tools@0.10.3
- jazz-react-native@0.10.3
## 1.0.64
### Patch Changes
- jazz-react-native@0.10.2
- jazz-tools@0.10.2
## 1.0.63
### Patch Changes
- Updated dependencies [5a63cba]
- jazz-tools@0.10.1
- jazz-react-native@0.10.1
## 1.0.62
### Patch Changes
- Updated dependencies [498954f]
- Updated dependencies [d42c2aa]
- Updated dependencies [dd03464]
- Updated dependencies [b426342]
- jazz-react-native@0.10.0
- jazz-tools@0.10.0
## 1.0.61
### Patch Changes
- jazz-react-native@0.9.23
- jazz-tools@0.9.23
## 1.0.60
### Patch Changes
- jazz-react-native@0.9.22
## 1.0.59
### Patch Changes
- Updated dependencies [1be017d]
- jazz-tools@0.9.21
- jazz-react-native@0.9.21
## 1.0.58
### Patch Changes
- Updated dependencies [b01cc1f]
- jazz-tools@0.9.20
- jazz-react-native@0.9.20
## 1.0.57
### Patch Changes
- jazz-react-native@0.9.19
- jazz-tools@0.9.19
## 1.0.56
### Patch Changes
- jazz-react-native@0.9.18
- jazz-tools@0.9.18
## 1.0.55
### Patch Changes
- Updated dependencies [c2ca1fe]
- Updated dependencies [1227047]
- jazz-tools@0.9.17
- jazz-react-native@0.9.17
## 1.0.54
### Patch Changes
- Updated dependencies [24b3b6a]
- jazz-tools@0.9.16
- jazz-react-native@0.9.16
## 1.0.53
### Patch Changes
- Updated dependencies [7491711]
- jazz-tools@0.9.15
- jazz-react-native@0.9.15
## 1.0.52
### Patch Changes
- Updated dependencies [3df93cc]
- jazz-tools@0.9.14
- jazz-react-native@0.9.14
## 1.0.51
### Patch Changes
- jazz-react-native@0.9.13
- jazz-tools@0.9.13
## 1.0.50
### Patch Changes
- jazz-react-native@0.9.12
- jazz-tools@0.9.12
## 1.0.49
### Patch Changes
- jazz-react-native@0.9.11
- jazz-tools@0.9.11
## 1.0.48
### Patch Changes
- Updated dependencies [f76274c]
- Updated dependencies [5e83864]
- jazz-react-native@0.9.10
- jazz-tools@0.9.10
## 1.0.47
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-react-native@0.9.9
## 1.0.46
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-react-native@0.9.8
## 1.0.45
### Patch Changes
- Updated dependencies [8a390d2]
- jazz-react-native@0.9.6
## 1.0.44
### Patch Changes
- Updated dependencies [c871912]
- jazz-react-native@0.9.5
## 1.0.43
### Patch Changes
- jazz-react-native@0.9.4
## 1.0.42
### Patch Changes
- Updated dependencies [7cd691f]
- jazz-react-native@0.9.3
## 1.0.41
### Patch Changes
- Updated dependencies [80fd3e9]
- jazz-react-native@0.9.2
## 1.0.40
### Patch Changes
- Updated dependencies [1b71969]
- jazz-tools@0.9.1
- jazz-react-native@0.9.1
## 1.0.39
### Patch Changes
- Updated dependencies [1da4d55]
- Updated dependencies [8eda792]
- Updated dependencies [1e5e3a1]
- jazz-react-native@0.9.0
- jazz-tools@0.9.0
## 1.0.38
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-react-native@0.8.51
## 1.0.37
### Patch Changes
- jazz-react-native@0.8.50
- jazz-tools@0.8.50
## 1.0.36
### Patch Changes
- jazz-react-native@0.8.49
- jazz-tools@0.8.49
## 1.0.35
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-react-native@0.8.48
## 1.0.34
### Patch Changes
- Updated dependencies [33ef9c4]
- jazz-react-native@0.8.47
## 1.0.33
### Patch Changes
- Updated dependencies [ab4ffbd]
- jazz-react-native@0.8.46
## 1.0.32
### Patch Changes
- Updated dependencies [7701307]
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- jazz-react-native@0.8.45
- jazz-tools@0.8.45
## 1.0.31
### Patch Changes
- jazz-react-native@0.8.44
- jazz-tools@0.8.44
## 1.0.30
### Patch Changes
- jazz-react-native@0.8.41
- jazz-tools@0.8.41
## 1.0.29
### Patch Changes
- Updated dependencies [0c6b0f3]
- Updated dependencies [249eecb]
- jazz-react-native@0.8.39
- jazz-tools@0.8.39
## 1.0.28
### Patch Changes
- jazz-react-native@0.8.38
- jazz-tools@0.8.38
## 1.0.27
### Patch Changes
- jazz-react-native@0.8.37
- jazz-tools@0.8.37
## 1.0.26
### Patch Changes
- Updated dependencies [c84764a]
- Updated dependencies [441fe27]
- jazz-react-native@0.8.36
- jazz-tools@0.8.36
## 1.0.25
### Patch Changes
- Updated dependencies [8b87117]
- jazz-tools@0.8.35
- jazz-react-native@0.8.35
## 1.0.24
### Patch Changes
- jazz-react-native@0.8.34
- jazz-tools@0.8.34
## 1.0.23
### Patch Changes
- Updated dependencies [df42b2b]
- jazz-tools@0.8.32
- jazz-react-native@0.8.32
## 1.0.22
### Patch Changes
- jazz-react-native@0.8.31
- jazz-tools@0.8.31
## 1.0.21
### Patch Changes
- jazz-react-native@0.8.30
- jazz-tools@0.8.30
## 1.0.20
### Patch Changes
- jazz-react-native@0.8.29
- jazz-tools@0.8.29
## 1.0.19
### Patch Changes
- jazz-react-native@0.8.28
- jazz-tools@0.8.28
## 1.0.18
### Patch Changes
- jazz-react-native@0.8.27
- jazz-tools@0.8.27
## 1.0.17
### Patch Changes
- Updated dependencies [d348c2d]
- Updated dependencies [6902b5b]
- Updated dependencies [1a0cd3d]
- jazz-tools@0.8.23
- jazz-react-native@0.8.23
## 1.0.16
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react-native@0.8.21
## 1.0.15
### Patch Changes
- Updated dependencies [3ef3ff3]
- jazz-react-native@0.8.20
## 1.0.14
### Patch Changes
- jazz-react-native@0.8.19
- jazz-tools@0.8.19
## 1.0.13
### Patch Changes
- Updated dependencies [048ac1d]
- jazz-tools@0.14.22
- jazz-expo@0.14.22
- jazz-react-native@0.8.18
- jazz-tools@0.8.18
## 1.0.12
### Patch Changes
- Updated dependencies [e7e505e]
- Updated dependencies [cfb6786]
- Updated dependencies [13b57aa]
- Updated dependencies [5662faa]
- Updated dependencies [2116a59]
- jazz-tools@0.14.21
- jazz-expo@0.14.21
- jazz-react-native@0.8.17
- jazz-tools@0.8.17
## 1.0.11
### Patch Changes
- Updated dependencies [6f72419]
- Updated dependencies [04b20c2]
- jazz-tools@0.14.20
- jazz-expo@0.14.20
- jazz-react-native@0.8.16
- jazz-tools@0.8.16
## 1.0.10
### Patch Changes
- Updated dependencies [061ec99]
- jazz-expo@0.14.19
- jazz-tools@0.14.19
- Updated dependencies [cce679b]
- jazz-tools@0.8.15
- jazz-react-native@0.8.15
## 1.0.9
### Patch Changes
- Updated dependencies [4b950bc]
- Updated dependencies [d6d9c0a]
- Updated dependencies [c559054]
- jazz-tools@0.14.18
- jazz-expo@0.14.18
- Updated dependencies [36273b3]
- jazz-tools@0.8.14
- jazz-react-native@0.8.14
## 1.0.8
### Patch Changes
- Updated dependencies [e512df4]
- jazz-tools@0.14.17
- jazz-expo@0.14.17
- Updated dependencies [fd011d7]
- jazz-tools@0.8.13
- jazz-react-native@0.8.13
## 1.0.7
### Patch Changes
- jazz-expo@0.14.16
- jazz-tools@0.14.16
- jazz-react-native@0.8.12
- jazz-tools@0.8.12
## 1.0.6
### Patch Changes
- jazz-expo@0.14.15
- jazz-tools@0.14.15
- jazz-react-native@0.8.11
- jazz-tools@0.8.11
## 1.0.5
### Patch Changes
- Updated dependencies [e32a1f7]
- jazz-tools@0.14.14
- jazz-expo@0.14.14
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
- Updated dependencies [b7639cf]
- jazz-react-native@0.8.8
## 1.0.4
### Patch Changes
- jazz-expo@0.14.13
- Updated dependencies [32b05b6]
- jazz-react-native@0.8.7
## 1.0.3
### Patch Changes
- jazz-expo@0.14.12
- jazz-react-native@0.8.6
## 1.0.2
### Patch Changes
- Updated dependencies [98d697f]
- jazz-expo@0.14.11
- Updated dependencies [c3f4e6b]
- Updated dependencies [d9152ed]
- jazz-react-native@0.8.5
- jazz-tools@0.8.5
## 1.0.1
### Patch Changes
- Updated dependencies [dc746a2]
- Updated dependencies [f869d9a]
- Updated dependencies [3fe6832]
- jazz-tools@0.14.10
- jazz-expo@0.14.10
- Updated dependencies
- jazz-react-native@0.8.3
- jazz-tools@0.8.3

View File

@@ -0,0 +1,24 @@
# 🎷 Jazz + Expo + `react-navigation` + Demo Auth
## 🚀 How to Run
### 1. Inside the Workspace Root
First, install dependencies and build the project:
```bash
pnpm i
pnpm run build
```
### 2. Inside the `examples/chat-rn-expo` Directory
Next, navigate to the specific example project and run the following commands:
```bash
pnpm expo prebuild
pnpx pod-install
pnpm expo run:ios
```
This will set up and launch the app on iOS. For Android, you can replace the last command with `pnpm expo run:android`.

View File

@@ -0,0 +1,55 @@
const { withBuildProperties } = require("expo-build-properties");
const { withDangerousMod } = require("@expo/config-plugins");
const fs = require("fs/promises");
const path = require("path");
/**
* https://github.com/mrousavy/nitro/issues/422#issuecomment-2545988256
*/
function withCustomIosMod(config) {
// Use expo-build-properties to bump iOS deployment target
config = withBuildProperties(config, { ios: { deploymentTarget: "16.0" } });
// Patch the generated Podfile fallback to ensure platform is always 16.0
config = withDangerousMod(config, [
"ios",
async (modConfig) => {
const podfilePath = path.join(
modConfig.modRequest.platformProjectRoot,
"Podfile",
);
let contents = await fs.readFile(podfilePath, "utf-8");
// Check if the IPHONEOS_DEPLOYMENT_TARGET setting is already present
// We search for the key being assigned, e.g., config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] =
const deploymentTargetSettingExists =
/\.build_settings\s*\[\s*['"]IPHONEOS_DEPLOYMENT_TARGET['"]\s*\]\s*=/.test(
contents,
);
if (!deploymentTargetSettingExists) {
// IPHONEOS_DEPLOYMENT_TARGET setting not found, proceed to add it.
contents = contents.replace(
/(post_install\s+do\s+\|installer\|[\s\S]*?)(\r?\n\s end\s*)$/m,
`$1
# Expo Build Properties: force deployment target
# https://github.com/mrousavy/nitro/issues/422#issuecomment-2545988256
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end
end
$2`,
);
}
await fs.writeFile(podfilePath, contents);
return modConfig;
},
]);
return config;
}
module.exports = ({ config }) => {
return withCustomIosMod(config);
};

View File

@@ -1,32 +1,39 @@
{
"expo": {
"name": "chat-rn-expo",
"slug": "chat-rn-expo",
"name": "jazz-chat-rn-expo",
"scheme": "jazz-chat-rn-expo",
"slug": "jazz-chat-rn-expo",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "tools.jazz.chatrnexpo"
"bundleIdentifier": "com.jazz.chatrn"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true,
"package": "tools.jazz.chatrnexpo"
"package": "com.jazz.chatrn"
},
"web": {
"favicon": "./assets/favicon.png"
"plugins": [
"expo-secure-store",
"expo-sqlite",
"expo-build-properties",
"expo-web-browser"
],
"extra": {
"eas": {
"projectId": "e0e61872-1906-4c84-b9d8-9be77355cad0"
}
},
"plugins": ["expo-secure-store", "expo-sqlite"]
"owner": "paxx"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,9 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};

View File

@@ -0,0 +1,27 @@
{
"cli": {
"version": ">= 12.5.1",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"ios-simulator": {
"extends": "development",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal"
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,4 @@
import "./polyfills";
import { registerRootComponent } from "expo";
import App from "./src/App";
registerRootComponent(App);

View File

@@ -1,9 +0,0 @@
import { registerRootComponent } from "expo";
import "./polyfills";
import App from "./src/App";
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

View File

@@ -0,0 +1,35 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const { FileStore } = require("metro-cache");
const path = require("path");
// eslint-disable-next-line no-undef
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot, { isCSSEnabled: true });
// Since we are using pnpm, we have to setup the monorepo manually for Metro
// #1 - Watch all files in the monorepo
config.watchFolders = [workspaceRoot];
// #2 - Try resolving with project modules first, then workspace modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.requireCycleIgnorePatterns = [
/(^|\/|\\)node_modules($|\/|\\)/,
/(^|\/|\\)packages($|\/|\\)/,
];
// Use turborepo to restore the cache when possible
config.cacheStores = [
new FileStore({
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
}),
];
// module.exports = config;
module.exports = withNativeWind(config, { input: "./global.css" });

View File

@@ -0,0 +1 @@
/// <reference types="nativewind/types" />

View File

@@ -1,32 +1,53 @@
{
"name": "chat-rn-expo",
"main": "index.ts",
"version": "1.0.113",
"main": "index.js",
"scripts": {
"build": "expo prebuild",
"build": "tsc --noEmit && expo export -p ios",
"start": "expo start",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"android": "expo run:android",
"ios": "expo run:ios",
"ios": "expo prebuild && pnpx pod-install && expo run:ios",
"web": "expo start --web"
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bacons/text-decoder": "^0.0.0",
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@bacons/text-decoder": "0.0.0",
"@craftzdog/react-native-buffer": "6.0.5",
"@react-native-community/netinfo": "11.4.1",
"expo": "54.0.0-canary-20250701-6a945c5",
"expo-clipboard": "^7.1.4",
"@react-navigation/native": "7.0.19",
"@react-navigation/native-stack": "7.2.1",
"clsx": "^2.0.0",
"expo": "53.0.8",
"expo-build-properties": "~0.14.6",
"expo-clipboard": "~7.1.4",
"expo-constants": "~17.1.6",
"expo-dev-client": "~5.1.8",
"expo-linking": "~7.1.4",
"expo-secure-store": "~14.2.3",
"expo-sqlite": "~15.2.10",
"expo-sqlite": "15.2.9",
"expo-status-bar": "~2.2.3",
"expo-web-browser": "~14.1.6",
"jazz-expo": "workspace:*",
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-native": "0.80.0",
"nativewind": "^4.1.21",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.2",
"react-native-get-random-values": "^1.11.0",
"readable-stream": "^4.7.0"
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "4.10.0",
"react-native-nitro-modules": "0.25.2",
"react-native-quick-crypto": "1.0.0-beta.15",
"react-native-url-polyfill": "^2.0.0",
"readable-stream": "4.7.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.10",
"typescript": "~5.8.3"
"@types/react": "~19.0.14",
"tailwindcss": "^3.4.17",
"typescript": "5.8.3"
},
"private": true
}
}

View File

@@ -3,6 +3,9 @@
// @ts-expect-error - @types/react-native doesn't cover this file
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
import { Buffer } from "@craftzdog/react-native-buffer";
polyfillGlobal("Buffer", () => Buffer);
// @ts-expect-error - @types/readable-stream doesn't have ReadableStream type
import { ReadableStream } from "readable-stream";
polyfillGlobal("ReadableStream", () => ReadableStream);

View File

@@ -1,18 +1,74 @@
import { JazzExpoProvider } from "jazz-tools/expo";
import React, { StrictMode } from "react";
import "../global.css";
import {
NavigationContainer,
useNavigationContainerRef,
} from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import * as Linking from "expo-linking";
import { RNQuickCrypto } from "jazz-expo/crypto";
import React, { StrictMode, useEffect, useState } from "react";
import HandleInviteScreen from "./invite";
import { JazzProvider } from "jazz-expo";
import { apiKey } from "./apiKey";
import ChatScreen from "./chat";
export default function App() {
const Stack = createNativeStackNavigator();
const prefix = Linking.createURL("/");
const linking = {
prefixes: [prefix],
config: {
screens: {
HandleInviteScreen: {
path: "router/invite/:valueHint?/:valueID/:inviteSecret",
},
},
},
};
function App() {
const [initialRoute, setInitialRoute] = useState<
"ChatScreen" | "HandleInviteScreen"
>("ChatScreen");
const navigationRef = useNavigationContainerRef();
useEffect(() => {
Linking.getInitialURL().then((url) => {
if (url) {
if (url && url.includes("invite")) {
setInitialRoute("HandleInviteScreen");
}
}
});
}, []);
return (
<StrictMode>
<JazzExpoProvider
<JazzProvider
CryptoProvider={RNQuickCrypto}
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
>
<ChatScreen />
</JazzExpoProvider>
<NavigationContainer linking={linking} ref={navigationRef}>
<Stack.Navigator initialRouteName={initialRoute}>
<Stack.Screen
options={{ title: "Jazz Chat" }}
name="ChatScreen"
// @ts-ignore
component={ChatScreen}
/>
<Stack.Screen
name="HandleInviteScreen"
component={HandleInviteScreen}
/>
</Stack.Navigator>
</NavigationContainer>
</JazzProvider>
</StrictMode>
);
}
export default App;

View File

@@ -1,6 +1,7 @@
import clsx from "clsx";
import * as Clipboard from "expo-clipboard";
import { Account, Group } from "jazz-tools";
import { useState } from "react";
import { Group, ID, Loaded, Profile } from "jazz-tools";
import { useEffect, useState } from "react";
import React, {
Button,
FlatList,
@@ -11,38 +12,54 @@ import React, {
TouchableOpacity,
View,
Alert,
StyleSheet,
} from "react-native";
import { useAccount, useCoState } from "jazz-tools/expo";
import { useAccount, useCoState } from "jazz-expo";
import { Chat, Message } from "./schema";
export default function ChatScreen() {
const { me, logOut } = useAccount(Account, { resolve: { profile: true } });
export default function ChatScreen({ navigation }: { navigation: any }) {
const { me, logOut } = useAccount();
const [chatId, setChatId] = useState<string>();
const [chatIdInput, setChatIdInput] = useState<string>();
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
const [message, setMessage] = useState("");
const profile = useCoState(Profile, me._refs.profile?.id, {});
function handleLogOut() {
setChatId(undefined);
logOut();
}
useEffect(() => {
navigation.setOptions({
headerRight: () => <Button onPress={handleLogOut} title="Logout" />,
headerLeft: () =>
loadedChat ? (
<Button
onPress={() => {
if (loadedChat?.id) {
Clipboard.setStringAsync(
`https://chat.jazz.tools/#/chat/${loadedChat.id}`,
);
Alert.alert("Copied to clipboard", `Chat ID: ${loadedChat.id}`);
}
}}
title="Share"
/>
) : null,
});
}, [navigation, loadedChat]);
const createChat = () => {
const group = Group.create();
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
const chat = Chat.create([], group);
const chat = Chat.create([], { owner: group });
setChatId(chat.id);
};
const joinChat = () => {
if (chatIdInput) {
if (chatIdInput.startsWith("https://chat.jazz.tools/#/chat/")) {
setChatId(chatIdInput.split("/").pop());
} else {
setChatId(chatIdInput);
}
setChatId(chatIdInput);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
@@ -58,28 +75,41 @@ export default function ChatScreen() {
}
};
const renderMessageItem = ({ item }: { item: Message }) => {
const renderMessageItem = ({ item }: { item: Loaded<typeof Message> }) => {
const isMe = item._edits?.text?.by?.isMe;
return (
<View
style={[
styles.messageContainer,
isMe ? styles.myMessage : styles.otherMessage,
]}
className={clsx(
`rounded-lg p-1 px-1.5 max-w-[80%] `,
isMe ? `bg-gray-200 self-end text-right` : `bg-gray-300 self-start `,
)}
>
{!isMe ? (
<Text
style={[
styles.messageSender,
{ textAlign: isMe ? "right" : "left" },
]}
className={clsx(
`text-xs text-gray-500`,
isMe ? "text-right" : "text-left",
)}
>
{item?._edits?.text?.by?.profile?.name}
</Text>
) : null}
<View style={styles.messageContent}>
<Text style={styles.messageText}>{item.text}</Text>
<Text style={[styles.messageTime, { marginTop: !isMe ? 8 : 4 }]}>
<View
className={clsx(
"flex relative items-end justify-between",
isMe ? "flex-row" : "flex-row",
)}
>
<Text className={clsx(`text-black text-md max-w-[85%]`)}>
{item.text}
</Text>
<Text
className={clsx(
"text-[10px] text-gray-500 text-right ml-2",
!isMe ? "mt-2" : "mt-1",
)}
>
{item?._edits?.text?.madeAt?.getHours().toString().padStart(2, "0")}
:
{item?._edits?.text?.madeAt
@@ -93,28 +123,31 @@ export default function ChatScreen() {
};
return (
<View style={styles.container}>
<View className="flex flex-col h-full">
{!loadedChat ? (
<View style={styles.welcomeContainer}>
<Text style={styles.usernameTitle}>Username</Text>
<View className="flex flex-col h-full items-center justify-center">
<Text className="text-m font-bold mb-6">Username</Text>
<TextInput
style={styles.usernameInput}
value={me?.profile.name ?? ""}
className="rounded h-12 p-2 mb-12 w-40 border border-gray-200 block"
value={profile?.name ?? ""}
onChangeText={(value) => {
if (me?.profile) {
me.profile.name = value;
if (profile) {
profile.name = value;
}
}}
textAlignVertical="center"
onSubmitEditing={sendMessage}
testID="username-input"
/>
<TouchableOpacity onPress={createChat} style={styles.newChatButton}>
<Text style={styles.newChatButtonText}>Start new chat</Text>
<TouchableOpacity
onPress={createChat}
className="bg-blue-500 p-4 rounded-md"
>
<Text className="text-white font-semibold">Start new chat</Text>
</TouchableOpacity>
<Text style={styles.joinChatTitle}>Join existing chat</Text>
<Text className="text-m font-bold mt-6">Join existing chat</Text>
<TextInput
style={styles.chatIdInput}
className="rounded h-12 p-2 m-2 mt-4 w-80 border border-gray-200 block"
placeholder="Chat ID"
value={chatIdInput ?? ""}
onChangeText={(value) => {
@@ -129,41 +162,21 @@ export default function ChatScreen() {
testID="chat-id-input"
/>
<TouchableOpacity
testID="join-chat-button"
onPress={joinChat}
style={styles.joinChatButton}
className="bg-green-500 p-4 rounded-md"
>
<Text style={styles.newChatButtonText}>Join chat</Text>
<Text className="text-white font-semibold">Join chat</Text>
</TouchableOpacity>
</View>
) : (
<>
<View style={styles.chatHeader}>
<Button
onPress={() => {
if (loadedChat?.id) {
Clipboard.setStringAsync(
`https://chat.jazz.tools/#/chat/${loadedChat.id}`,
);
Alert.alert(
"Copied to clipboard",
`Chat ID: ${loadedChat.id}`,
);
}
}}
title="Share"
/>
<Text style={{ fontWeight: "bold", fontSize: 18 }}>Jazz chat</Text>
<Button onPress={handleLogOut} title="Logout" />
</View>
<FlatList
contentContainerStyle={{
flexGrow: 1,
gap: 6,
padding: 8,
justifyContent: "flex-end",
}}
style={styles.messageList}
className="flex"
data={loadedChat}
keyExtractor={(item) => item.id}
renderItem={renderMessageItem}
@@ -172,11 +185,11 @@ export default function ChatScreen() {
<KeyboardAvoidingView
keyboardVerticalOffset={110}
behavior="padding"
style={styles.inputContainer}
className="p-3 bg-white border-t border-gray-300"
>
<SafeAreaView style={styles.inputRow}>
<SafeAreaView className="flex flex-row items-center gap-2">
<TextInput
style={styles.messageInput}
className="rounded-full h-8 py-0 px-2 border border-gray-200 block flex-1"
value={message}
onChangeText={setMessage}
placeholder="Type a message..."
@@ -186,7 +199,7 @@ export default function ChatScreen() {
/>
<TouchableOpacity
onPress={sendMessage}
style={styles.sendButton}
className="bg-gray-300 text-white rounded-full h-8 w-8 items-center justify-center"
testID="send-button"
>
<Text></Text>
@@ -198,133 +211,3 @@ export default function ChatScreen() {
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
height: "100%",
},
chatHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
padding: 8,
paddingTop: 48,
backgroundColor: "#f0f0f0",
},
welcomeContainer: {
flex: 1,
flexDirection: "column",
height: "100%",
alignItems: "center",
justifyContent: "center",
},
usernameTitle: {
fontSize: 16,
fontWeight: "bold",
marginBottom: 24,
},
usernameInput: {
borderRadius: 4,
height: 48,
padding: 8,
marginBottom: 48,
width: 160,
borderWidth: 1,
borderColor: "#e5e5e5",
},
newChatButton: {
backgroundColor: "#3b82f6",
padding: 16,
borderRadius: 6,
},
newChatButtonText: {
color: "white",
fontWeight: "600",
},
joinChatTitle: {
fontSize: 16,
fontWeight: "bold",
marginTop: 24,
},
chatIdInput: {
borderRadius: 4,
height: 48,
padding: 8,
margin: 8,
marginTop: 16,
width: 320,
borderWidth: 1,
borderColor: "#e5e5e5",
},
joinChatButton: {
backgroundColor: "#22c55e",
padding: 16,
borderRadius: 6,
},
messageList: {
display: "flex",
},
messageContainer: {
borderRadius: 8,
padding: 4,
paddingHorizontal: 6,
maxWidth: "80%",
},
myMessage: {
backgroundColor: "#e5e5e5",
alignSelf: "flex-end",
},
otherMessage: {
backgroundColor: "#d4d4d4",
alignSelf: "flex-start",
},
messageSender: {
fontSize: 12,
color: "#6b7280",
},
messageContent: {
flexDirection: "row",
alignItems: "flex-end",
justifyContent: "space-between",
},
messageText: {
color: "black",
fontSize: 16,
maxWidth: "85%",
},
messageTime: {
fontSize: 10,
color: "#6b7280",
marginLeft: 8,
},
inputContainer: {
padding: 12,
backgroundColor: "white",
borderTopWidth: 1,
borderTopColor: "#d4d4d4",
},
inputRow: {
flexDirection: "row",
alignItems: "center",
gap: 8,
},
messageInput: {
borderRadius: 9999,
height: 32,
paddingVertical: 0,
paddingHorizontal: 8,
borderWidth: 1,
borderColor: "#e5e5e5",
flex: 1,
},
sendButton: {
backgroundColor: "#d4d4d4",
borderRadius: 9999,
height: 32,
width: 32,
alignItems: "center",
justifyContent: "center",
},
});

View File

@@ -0,0 +1,18 @@
import { useAcceptInvite } from "jazz-expo";
import React, { Text } from "react-native";
import { Chat } from "./schema";
export default function HandleInviteScreen({
navigation,
}: {
navigation: any;
}) {
useAcceptInvite({
invitedObjectSchema: Chat,
onAccept: async (chatId) => {
navigation.navigate("ChatScreen", { chatId });
},
});
return <Text>Accepting invite...</Text>;
}

View File

@@ -3,7 +3,5 @@ import { co, z } from "jazz-tools";
export const Message = co.map({
text: z.string(),
});
export type Message = co.loaded<typeof Message>;
export const Chat = co.list(Message);
export type Chat = co.loaded<typeof Chat>;

View File

@@ -0,0 +1,14 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -2,7 +2,7 @@
# fully erased. The tap into the input field hits the middle, and clears all
# text to the left. If there's more to the right, it slides left, and thus we
# repeat this step. https://maestro.mobile.dev/api-reference/commands/erasetext
appId: tools.jazz.chatrnexpo
appId: com.jazz.chatrn
---
- copyTextFrom:
id: ${id}

View File

@@ -1,4 +1,4 @@
appId: tools.jazz.chatrnexpo
appId: com.jazz.chatrn
---
- launchApp
@@ -8,7 +8,10 @@ appId: tools.jazz.chatrnexpo
# - assertVisible: "Reload"
# - tapOn: "Reload"
- assertVisible: "Username"
# login
- assertVisible: "Logout"
- tapOn: "Logout"
- assertVisible: "Anonymous user"
- runFlow:
label: "Erase existing username"
file: erase_text.yml
@@ -40,18 +43,12 @@ appId: tools.jazz.chatrnexpo
# logout
- tapOn: "Logout"
- assertVisible: "Username"
- assertVisible: "Anonymous user"
- extendedWaitUntil:
visible: "Anonymous user"
timeout: 10000
# join chat
## Commented because it fails on CI
# - tapOn:
# id: "chat-id-input"
# - inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
# - tapOn:
# id: "join-chat-button"
# - assertVisible: "boorad"
# - assertVisible: "bro, low key, it do be like that tho"
# join chat
- tapOn:
id: "chat-id-input"
- inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
- tapOn: "Join chat"
- assertVisible: "boorad"
- assertVisible: "bro, low key, it do be like that tho"

View File

@@ -5,15 +5,11 @@
set -e
OUTPUT_FILE=~/output/android-install.log
mkdir -p ~/output
touch $OUTPUT_FILE
# build and install the app
echo "Building and installing Android app."
echo "If it fails, its output will be in artifact: android-install.log..."
cd ./android/
./gradlew installRelease >> $OUTPUT_FILE 2>&1
./gradlew installRelease >> ~/output/android-install.log 2>&1
cd ..
# run the e2e tests

View File

@@ -1,13 +0,0 @@
#!/bin/bash
# This script is necessary, because unlike ios, the android emulator action
# accepts a script, runs it as your tests, then terminates.
set -e
# run the e2e tests
export PATH="$PATH":"$HOME/.maestro/bin"
export MAESTRO_DRIVER_STARTUP_TIMEOUT=300000 # setting to 5 mins 👀
export MAESTRO_CLI_NO_ANALYTICS=1
export MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED=true
maestro test test/e2e/flow.yml

View File

@@ -1,6 +1,8 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
"strict": true,
"moduleResolution": "bundler"
},
"exclude": ["src/tests"]
}

View File

@@ -73,9 +73,3 @@ yarn-error.log
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -1,179 +1,5 @@
# chat-rn
## 1.0.140
### Patch Changes
- Updated dependencies [57fb69f]
- Updated dependencies [048ac1d]
- cojson@0.14.22
- jazz-tools@0.14.22
- cojson-transport-ws@0.14.22
- jazz-react-native@0.14.22
## 1.0.139
### Patch Changes
- Updated dependencies [e7e505e]
- Updated dependencies [c3d8779]
- Updated dependencies [13b57aa]
- Updated dependencies [5662faa]
- Updated dependencies [2116a59]
- jazz-tools@0.14.21
- cojson@0.14.21
- jazz-react-native@0.14.21
- cojson-transport-ws@0.14.21
## 1.0.138
### Patch Changes
- Updated dependencies [6f72419]
- Updated dependencies [04b20c2]
- jazz-tools@0.14.20
- jazz-react-native@0.14.20
- cojson@0.14.20
- cojson-transport-ws@0.14.20
## 1.0.137
### Patch Changes
- Updated dependencies [061ec99]
- jazz-react-native@0.14.19
- cojson@0.14.19
- cojson-transport-ws@0.14.19
- jazz-tools@0.14.19
## 1.0.136
### Patch Changes
- Updated dependencies [4b950bc]
- Updated dependencies [0d5ee3e]
- Updated dependencies [d6d9c0a]
- Updated dependencies [c559054]
- jazz-tools@0.14.18
- cojson@0.14.18
- jazz-react-native@0.14.18
- cojson-transport-ws@0.14.18
## 1.0.135
### Patch Changes
- Updated dependencies [e512df4]
- jazz-tools@0.14.17
- jazz-react-native@0.14.17
## 1.0.134
### Patch Changes
- Updated dependencies [5e253cc]
- cojson@0.14.16
- cojson-transport-ws@0.14.16
- jazz-react-native@0.14.16
- jazz-tools@0.14.16
## 1.0.133
### Patch Changes
- Updated dependencies [23daa7c]
- cojson@0.14.15
- jazz-react-native@0.14.15
- cojson-transport-ws@0.14.15
- jazz-tools@0.14.15
## 1.0.132
### Patch Changes
- Updated dependencies [e32a1f7]
- jazz-tools@0.14.14
- jazz-react-native@0.14.14
## 1.0.131
### Patch Changes
- jazz-react-native@0.14.13
## 1.0.130
### Patch Changes
- jazz-react-native@0.14.12
## 1.0.129
### Patch Changes
- Updated dependencies [98d697f]
- jazz-react-native@0.14.11
## 1.0.128
### Patch Changes
- Updated dependencies [dc746a2]
- Updated dependencies [f869d9a]
- Updated dependencies [3fe6832]
- jazz-react-native@0.14.10
- jazz-tools@0.14.10
## 1.0.127
### Patch Changes
- Updated dependencies [22c2600]
- jazz-tools@0.14.9
- jazz-react-native@0.14.9
## 1.0.126
### Patch Changes
- Updated dependencies [637ae13]
- jazz-tools@0.14.8
- jazz-react-native@0.14.8
## 1.0.125
### Patch Changes
- Updated dependencies [365b0ea]
- jazz-tools@0.14.7
- jazz-react-native@0.14.7
## 1.0.124
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react-native@0.14.6
## 1.0.123
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react-native@0.14.5
## 1.0.122
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react-native@0.14.4
## 1.0.121
### Patch Changes

View File

@@ -26,7 +26,7 @@ This will set up and launch the app on iOS. For Android, you can skip `pnpm pods
This was created using the following command:
```bash
pnpx @react-native-community/cli init chat-rn --version 0.76.9 --install-pods true --skip-git-init true --package-name com.chatrn --directory chat-rn
pnpx @react-native-community/cli init chat-rn --version 0.76.7 --install-pods true --skip-git-init true --package-name com.chatrn --directory chat-rn
```
Then change package name in `package.json`, and begin build instructions above.

View File

@@ -11,11 +11,11 @@ react {
// The root of your project, i.e. where "package.json" lives. Default is '../..'
// root = file("../../")
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
reactNativeDir = file("../../../../node_modules/react-native")
// reactNativeDir = file("../../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
codegenDir = file("../../../../node_modules/@react-native/codegen")
// codegenDir = file("../../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
cliFile = file("../../../../node_modules/react-native/cli.js")
// cliFile = file("../../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
@@ -63,14 +63,14 @@ def enableProguardInReleaseBuilds = false
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
def jscFlavor = 'org.webkit:android-jsc:+'
android {
ndkVersion rootProject.ext.ndkVersion

View File

@@ -4,11 +4,13 @@ import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
class MainApplication : Application(), ReactApplication {
@@ -33,6 +35,10 @@ class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
loadReactNative(this)
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
}
}

View File

@@ -3,9 +3,9 @@ buildscript {
buildToolsVersion = "35.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "27.1.12297006"
kotlinVersion = "2.1.20"
targetSdkVersion = 34
ndkVersion = "26.1.10909125"
kotlinVersion = "1.9.25"
}
repositories {
google()

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +115,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -205,7 +206,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -213,7 +214,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.

Some files were not shown because too many files have changed in this diff Show More