The offline-first mobile recorder for Smart MoM — capture meetings on the go, upload resilient audio chunks, and sync seamlessly with the Smart MoM backend.
This repo is the mobile client. It is designed to work with the Smart MoM backend (FastAPI + MongoDB + Redis + Qdrant + S3) described in the main Smart MoM project.
Architecture | Features | Technology Stack | Project Structure | Setup & Running | Recording Flow | Backend Integration | Known Limitations
App name: Smart MoM (mobile)
Stack: Expo · React Native · TypeScript · expo-audio · AsyncStorage · Secure Store · NetInfo
Works with: FastAPI Smart MoM backend · MongoDB Atlas · Redis · Qdrant · AWS S3 · ffmpeg
┌──────────────────────────────────────────────────┐
│ Smart MoM Mobile │
│ Expo / React Native app │
│ - Login / Register │
│ - Meeting metadata form │
│ - Chunk-based recording via expo-audio │
│ - Offline chunk queue (AsyncStorage + FS) │
│ - Background-safe recording + notifications │
└──────────────────────────────┬──────────────────┘
│ HTTPS (JWT auth)
▼
┌──────────────────────────────────────────────────┐
│ Smart MoM Backend (FastAPI) │
│ /auth/login, /auth/register, /auth/logout │
│ /api/v1/meetings/ │
│ /api/v1/meetings/{id}/chunks │
│ /api/v1/meetings/{id}/merge-chunks │
└─────────────┬───────────────┬──────────────────┘
│ │
▼ ▼
MongoDB Redis
(meetings, (JWT
users) blacklist)
│
▼
Qdrant + OpenAI + S3 + ffmpeg
(transcription, RAG, audio storage)
The mobile app focuses solely on reliable recording and upload. All transcription, summarisation, RAG, and chat features live in the backend / web dashboard.
-
Email + password authentication
- Secure token storage via Expo Secure Store
- Friendly, backend-aware error messages (invalid creds, expired tunnel, backend down)
-
Chunk-based audio recording (expo-audio)
- High-quality MP4/AAC recording
- Fixed chunk interval (
CHUNK_INTERVAL_MS, default 30s) - Each chunk finalised and uploaded as self-contained MP4 (ffmpeg-ready on backend)
-
Offline-first uploads
- If upload fails (no network / timeout), chunks are:
- Copied into app cache directory
- Enqueued in AsyncStorage with metadata
- Automatically retried when network comes back
- Queue is resumed on app start so no recorded audio is lost accidentally
- If upload fails (no network / timeout), chunks are:
-
Background & screen-off support
activateKeepAwakeAsyncto keep device awake during recording- Audio mode configured with
allowsBackgroundRecordingandshouldPlayInBackground - Catch-up chunk cycle if OS suspends JS and app returns to foreground
- Persistent notification while recording (Android)
-
Meeting metadata
- Capture attendees, context, and number of participants along with audio
- Creates meeting in backend before recording starts
-
Backend URL configuration
- Runtime-configurable backend URL stored in AsyncStorage
- Defaults to
EXPO_PUBLIC_BACKEND_URL(orhttp://localhost:8000)
| Component | Technology / Package |
|---|---|
| Framework | Expo SDK 54 (managed) |
| Runtime | React Native 0.81, React 19 |
| Language | TypeScript 5 |
| Navigation | Expo Router 6 |
| Audio Recording | expo-audio (high quality MP4/AAC) |
| Auth Token Storage | expo-secure-store |
| Offline Queue | AsyncStorage + expo-file-system/legacy |
| Network Status | @react-native-community/netinfo |
| Background | expo-keep-awake, notifications helper |
| UI | React Native core components + custom theme |
The app assumes the Smart MoM FastAPI backend with:
/auth/*endpoints (JWT-based)/api/v1/meetings/*meeting + chunk endpoints- MongoDB Atlas, Redis, Qdrant, OpenAI, AWS S3, ffmpeg
momai-v7/
├── app/
│ ├── _layout.tsx # Expo Router root layout
│ ├── index.tsx # Auth gate → /record or /auth/login
│ ├── auth/
│ │ ├── login.tsx # Login screen (email/password)
│ │ └── register.tsx # Registration screen
│ ├── record.tsx # Recording screen (uses useChunkRecorder)
│ └── settings.tsx # Backend URL / settings screen
│
├── src/
│ ├── constants/
│ │ └── index.ts # Colors, backend URL helpers, chunk interval
│ ├── hooks/
│ │ └── useChunkRecorder.ts
│ │ # Core recording + chunk cycle + offline queue
│ └── services/
│ ├── auth.ts # login / register / logout, authFetch wrapper
│ ├── meetings.ts # createMeeting, uploadChunk, mergeChunks
│ ├── chunkQueue.ts # AsyncStorage-backed queue of offline chunks
│ └── backgroundNotification.ts
│ # Recording notification helpers
│
├── assets/
│ ├── Mom.png
│ └── smart-mom.png # App icon & splash
│
├── app.json # Expo app config (permissions, icons, bundle IDs)
├── babel.config.js
├── metro.config.js
├── package.json
├── tsconfig.json
└── eas.json # EAS build profiles (if using Expo Application Services)- Node.js 20+
- npm 10+ or Yarn
- Expo CLI (
npm install -g expo-clioptional) - A running Smart MoM backend (FastAPI) or tunnel URL (Cloudflare, ngrok, etc.)
For local backend development, make sure the FastAPI service exposes:
/auth/login/auth/register/auth/logout/api/v1/meetings//api/v1/meetings/{id}/chunks/api/v1/meetings/{id}/merge-chunks
The mobile app reads the backend URL from a public Expo env:
# .env (or .env.development for EAS)
EXPO_PUBLIC_BACKEND_URL="http://localhost:8000"EXPO_PUBLIC_BACKEND_URL:- Default backend URL used on first launch.
- Can be overridden at runtime inside the app (Settings), persisted via AsyncStorage.
Only
EXPO_PUBLIC_*variables are available on the client. Non-EXPO_PUBLIC_keys will not be accessible.
# 1. Install dependencies
npm install
# or
yarn install
# 2. (Optional) create .env for backend URL
echo EXPO_PUBLIC_BACKEND_URL="http://localhost:8000" > .env
# 3. Start the Expo dev server
npm run start
# or
npx expo start --offline- Scan the QR code with the Expo Go app (iOS/Android) or run on a simulator:
- Android:
npm run android - iOS:
npm run ios
- Android:
When testing against a local backend from a device, ensure:
- Device and backend machine are on the same network
EXPO_PUBLIC_BACKEND_URLis set to your machine IP (e.g.http://192.168.0.10:8000)- Any tunnel (Cloudflare, ngrok) URLs are reachable from the device
If you use Expo Application Services:
# 1. Configure eas.json (already present)
# 2. Login to Expo
npx expo login
# 3. Configure project
npx expo prebuild # if you need to customize native code (optional)
# 4. Trigger a build
npx expo build:android
# or
npx expo build:iosRefer to eas.json for pre-configured build profiles.
The recording lifecycle is implemented in useChunkRecorder:
-
Start recording
- Request microphone permission via
requestRecordingPermissionsAsync activateKeepAwakeAsyncto prevent sleep- Configure audio mode using
setAudioModeAsync:playsInSilentMode: trueallowsRecording: trueshouldPlayInBackground: trueallowsBackgroundRecording: true
- Call
createMeetingon the backend with:meeting_id(client-generatedrec_<timestamp>_<rand>)attendees[],context,no_of_personsaudio_state = "chunked"(backend knows audio is chunk-based)
- Start
expo-audiorecorder usingprepareToRecordAsync()+record() - Start wall-clock-based elapsed timer
- Request microphone permission via
-
Chunk cycle (every
CHUNK_INTERVAL_MS, default 30s)For each cycle:
recorder.stop()finalises the current file- Validate the file via
FileSystem.getInfoAsync:- Must exist and be bigger than
MIN_CHUNK_BYTES(default 1000 bytes)
- Must exist and be bigger than
- Copy file into a safe cached path (one per meeting/chunk) via
safeCopyChunkFile - Restart recording immediately:
prepareToRecordAsync()→record()
- Upload the safe cached file via
uploadChunk:POST /api/v1/meetings/{id}/chunksmultipart/form-data, fieldaudio, MIMEaudio/mp4- Parameters:
chunk_id,timestamp
-
Stop recording
- Stop timers and chunk scheduler
- Stop recorder and validate the final chunk
- Upload final chunk (or queue if offline)
- Drain any queued chunks for the meeting if online
- Leave
audio_stateaschunkedfor the backend to later call/merge-chunks
If chunk upload fails:
-
Chunk is copied to a stable cache location
-
Entry is enqueued in AsyncStorage via
enqueuePreCachedChunk -
UI shows status as
queued, andpendingCountincreases -
On:
- App start, or
- Network back-online event
the app calls
drainQueueForMeetingto retry all queued uploads.
The queue persists across app restarts, ensuring that no valid recorded audio is lost.
activateKeepAwakeAsync('momai_recording')is used to keep the app awake while recording.allowsBackgroundRecordingis set to protect against incidental backgrounding.- When app returns to foreground:
- Elapsed timer is resynchronised using wall-clock time.
- If a chunk cycle is overdue, a catch-up cycle is executed immediately.
- Android uses a foreground notification while recording (via
backgroundNotificationhelpers).
From src/services/auth.ts:
| Method | Path | Description |
|---|---|---|
POST |
/auth/login |
Email + password login |
POST |
/auth/register |
Account registration |
POST |
/auth/logout |
Blacklist current JWT (best-effort) |
- Tokens are stored with
expo-secure-storeunder keyauth_token. authFetchaddsAuthorization: Bearer <token>and handles:- Timeouts (default 15s)
- Offline / unreachable server
- Friendly error mapping for 401/403/422/502/503/504
From src/services/meetings.ts:
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/meetings/ |
Create a new meeting (metadata only) |
POST |
/api/v1/meetings/{id}/chunks |
Upload a single audio chunk (multipart) |
POST |
/api/v1/meetings/{id}/merge-chunks |
(Optional) Trigger merge after upload complete |
The backend is responsible for:
- Detecting mobile MP4 chunks
- Using ffmpeg concat demuxer to merge them correctly
- Updating
audio_statefrom"chunked"→"ready"- Handing off to transcription + RAG pipelines
The app reads and persists the backend URL via src/constants/index.ts:
- Default:
DEFAULT_BACKEND_URL = process.env.EXPO_PUBLIC_BACKEND_URL ?? 'http://localhost:8000' - Runtime override:
- Stored under key
momai_backend_urlin AsyncStorage initBackendUrl()loads saved value on app startsetBackendUrl()updates both in-memory_runtimeUrland AsyncStorage
- Stored under key
In UI:
- The Settings screen allows users to:
- View the current backend URL
- Update it for tunnels / staging / production
- Show hints if the tunnel (e.g. Cloudflare) has expired (based on HTTP 502/503/504 codes)
-
No in-app transcription or summaries
The mobile app only records and uploads audio. All transcription, summarisation, and chat features are handled by the backend and web dashboard. -
Backend contract required
The app assumes a Smart MoM-compatible backend (FastAPI routes and response shapes). Using a different backend requires matching endpoints or adapting the service layer. -
No in-app playback
Playback / viewing of meetings is expected via the web dashboard. This app does not currently provide audio playback UI. -
Single backend at a time
The app targets one backend URL at a time (with runtime override). Multi-environment switching is manual via Settings.
Smart MoM Mobile is built and maintained as an AI meeting intelligence platform by INT. For questions, issues, or contributions, please open a GitHub issue or pull request.