3372a93f71
Expo SDK 54 switched to precompiled React Native XCFrameworks by default for faster iOS builds. However, the precompiled frameworks do not expose legacy bridge headers (RCTBridge, RCTViewManager, RCTSurfaceHostingProxyRootView, RCTPackagerConnection, RCTDevSettings.isDebuggingRemotely, rootViewFactory) that some native modules (e.g. expo-dev-client) still reference. Add expo-build-properties (v1.0.10, the SDK 54-compatible version) and configure buildReactNativeFromSource: true for iOS. This compiles React Native from source, making all native headers available to linked modules and resolving the Xcode compilation errors seen in the EAS production build. Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
255 lines
8.8 KiB
Markdown
255 lines
8.8 KiB
Markdown
# Mobile App
|
||
|
||
DocuElevate includes a native mobile application for iOS and Android built with **React Native** and **Expo**. The app allows users to capture documents with the device camera, pick files from the device storage, and receive push notifications when documents finish processing.
|
||
|
||
## Features
|
||
|
||
| Feature | iOS | Android |
|
||
|---------|-----|---------|
|
||
| SSO login (OAuth2) | ✅ | ✅ |
|
||
| Local / basic auth login | ✅ | ✅ |
|
||
| Auto-generated API token | ✅ | ✅ |
|
||
| Camera capture → upload | ✅ | ✅ |
|
||
| File picker upload | ✅ | ✅ |
|
||
| Share Sheet / Share Intent | ✅ | ✅ |
|
||
| Push notifications | ✅ | ✅ |
|
||
| Document list | ✅ | ✅ |
|
||
| Dark mode | ✅ | ✅ |
|
||
|
||
## Getting Started (Development)
|
||
|
||
### Prerequisites
|
||
|
||
- Node.js 18 or later
|
||
- [Expo CLI](https://docs.expo.dev/get-started/installation/): `npm install -g @expo/cli`
|
||
- [Expo Go](https://expo.dev/client) app on your iOS or Android device (for development)
|
||
- A running DocuElevate server reachable from your device
|
||
|
||
### Run in development mode
|
||
|
||
```bash
|
||
cd mobile
|
||
npm install
|
||
npx expo start
|
||
```
|
||
|
||
Scan the QR code with **Expo Go** on your device. On iOS you can also use the Camera app.
|
||
|
||
## Building for Production
|
||
|
||
DocuElevate uses **Expo Application Services (EAS)** to produce App Store / Play Store binaries.
|
||
|
||
```bash
|
||
# Install EAS CLI globally
|
||
npm install -g eas-cli
|
||
|
||
# Authenticate with Expo
|
||
eas login
|
||
|
||
# Build for iOS (requires Apple Developer account)
|
||
eas build --platform ios
|
||
|
||
# Build for Android
|
||
eas build --platform android
|
||
```
|
||
|
||
> **Note:** The mobile app uses `expo-build-properties` with `buildReactNativeFromSource: true` for iOS builds. This is required for Expo SDK 54 (React Native 0.81) compatibility — some native modules still use legacy bridge APIs (`RCTBridge`, `RCTViewManager`, etc.) that are no longer included in the default precompiled XCFrameworks. Building React Native from source makes these headers available, at the cost of slightly longer iOS build times.
|
||
|
||
See the [EAS Build documentation](https://docs.expo.dev/build/introduction/) for full setup instructions.
|
||
|
||
## Authentication
|
||
|
||
### SSO Login Flow
|
||
|
||
The mobile app uses the server's existing OAuth2/SSO setup:
|
||
|
||
1. User enters the DocuElevate server URL on the login screen.
|
||
2. The app opens `<server>/login?mobile=1&redirect_uri=docuelevate://callback` in the **system browser** (Safari / Chrome).
|
||
3. The user authenticates via SSO or local credentials.
|
||
4. The server redirects back to `docuelevate://callback`.
|
||
5. The app calls `POST /api/mobile/generate-token` to exchange the session for a **long-lived API token**.
|
||
6. The token is stored securely in the device's keychain (`expo-secure-store`).
|
||
|
||
### Auto-generated Mobile Token
|
||
|
||
When the mobile app completes login it automatically creates a named API token (`"Mobile App – <device name>"`) via `POST /api/mobile/generate-token`. This token:
|
||
|
||
- Works identically to tokens created manually in the web UI.
|
||
- Is shown in the **API Tokens** page (`/api-tokens`) and can be revoked there.
|
||
- Is stored in the device's secure keychain, never in plain storage.
|
||
|
||
## Push Notifications
|
||
|
||
Push notifications are delivered via the **Expo Push Notification** service, which routes through Apple Push Notification service (APNs) for iOS and Firebase Cloud Messaging (FCM) for Android.
|
||
|
||
**No server-side APNs/FCM credentials are required** – Expo's servers handle the provider integration.
|
||
|
||
### How it works
|
||
|
||
1. After login, the app requests notification permission from the operating system.
|
||
2. If granted, the app obtains an **Expo Push Token** (`ExponentPushToken[…]`).
|
||
3. The token is registered with the backend via `POST /api/mobile/register-device`.
|
||
4. When a document finishes processing, the server sends a push notification to all registered devices for that user.
|
||
|
||
### Managing registered devices
|
||
|
||
Users can see and remove their registered devices from the **Profile** tab in the app, or via the API:
|
||
|
||
```bash
|
||
# List registered devices
|
||
curl -H "Authorization: Bearer <token>" https://your-server/api/mobile/devices
|
||
|
||
# Remove a device
|
||
curl -X DELETE -H "Authorization: Bearer <token>" https://your-server/api/mobile/devices/<id>
|
||
```
|
||
|
||
## Uploading Documents
|
||
|
||
### Camera Capture
|
||
|
||
1. Open the **Upload** tab.
|
||
2. Tap **Camera**.
|
||
3. Point the camera at the document and take a photo.
|
||
4. The image is immediately uploaded and queued for processing.
|
||
|
||
### File Picker
|
||
|
||
1. Open the **Upload** tab.
|
||
2. Tap **File Picker**.
|
||
3. Browse to and select one or more files (PDF, DOCX, images, etc.).
|
||
4. Files are uploaded and queued for processing.
|
||
|
||
### Share Sheet (iOS) / Share Intent (Android)
|
||
|
||
The app registers itself as a share target so any file can be sent directly to DocuElevate from another app:
|
||
|
||
1. Open a file in Files, Mail, Safari, or any other app.
|
||
2. Tap the **Share** button (iOS) or **Share** (Android).
|
||
3. Find and tap **DocuElevate** in the share sheet.
|
||
4. The file is immediately uploaded.
|
||
|
||
> **Note:** The app must be installed on the device for it to appear in the share sheet.
|
||
|
||
## Mobile API Endpoints
|
||
|
||
The backend exposes a dedicated `/api/mobile/` namespace:
|
||
|
||
| Method | Endpoint | Auth | Description |
|
||
|--------|----------|------|-------------|
|
||
| `POST` | `/api/mobile/generate-token` | Session | Exchange SSO session for API token |
|
||
| `POST` | `/api/mobile/register-device` | Bearer | Register Expo push token |
|
||
| `GET` | `/api/mobile/devices` | Bearer | List registered devices |
|
||
| `DELETE` | `/api/mobile/devices/{id}` | Bearer | Deactivate a device |
|
||
| `GET` | `/api/mobile/whoami` | Bearer | Get current user profile |
|
||
|
||
All other API endpoints (file upload, file listing, etc.) work with Bearer token authentication.
|
||
|
||
### POST /api/mobile/generate-token
|
||
|
||
Exchanges an active web session (cookie) for a permanent API token suitable for use in the mobile app.
|
||
|
||
**Request:**
|
||
```json
|
||
{ "device_name": "John's iPhone" }
|
||
```
|
||
|
||
**Response (201):**
|
||
```json
|
||
{
|
||
"token": "de_AbCdEfGhIjKl...",
|
||
"token_id": 42,
|
||
"name": "Mobile App – John's iPhone",
|
||
"created_at": "2026-03-10T09:30:00Z"
|
||
}
|
||
```
|
||
|
||
> ⚠️ The `token` value is returned **once only**. Store it in the device's secure keychain immediately.
|
||
|
||
### POST /api/mobile/register-device
|
||
|
||
Registers an Expo push token for the authenticated user.
|
||
|
||
**Request:**
|
||
```json
|
||
{
|
||
"push_token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
|
||
"device_name": "John's iPhone",
|
||
"platform": "ios"
|
||
}
|
||
```
|
||
|
||
Supported platforms: `ios`, `android`, `web`.
|
||
|
||
Re-registering the same token is safe (idempotent).
|
||
|
||
### GET /api/mobile/whoami
|
||
|
||
Returns the current user's profile.
|
||
|
||
**Response (200):**
|
||
```json
|
||
{
|
||
"owner_id": "john@example.com",
|
||
"display_name": "John Doe",
|
||
"email": "john@example.com",
|
||
"avatar_url": "https://www.gravatar.com/avatar/...",
|
||
"is_admin": false
|
||
}
|
||
```
|
||
|
||
## Configuration
|
||
|
||
No server-side configuration is required to enable the mobile app. The Expo push notification routing does not need FCM or APNs credentials on the server.
|
||
|
||
If you wish to use **direct FCM/APNs** without Expo's relay, replace the `send_expo_push_notification` function in `app/utils/push_notification.py` with your own implementation.
|
||
|
||
## Project Structure (mobile/)
|
||
|
||
```
|
||
mobile/
|
||
├── App.tsx # Root component
|
||
├── app.json # Expo/EAS configuration
|
||
├── eas.json # EAS Build profiles
|
||
├── package.json
|
||
├── tsconfig.json
|
||
└── src/
|
||
├── context/
|
||
│ └── AuthContext.tsx # Auth state + SSO login flow
|
||
├── hooks/
|
||
│ └── usePushNotifications.ts # Push token registration
|
||
├── screens/
|
||
│ ├── LoginScreen.tsx # Server URL + SSO button
|
||
│ ├── UploadScreen.tsx # Camera capture + file picker
|
||
│ ├── FilesScreen.tsx # Processed document list
|
||
│ └── ProfileScreen.tsx # User profile + sign out
|
||
└── services/
|
||
└── api.ts # DocuElevate REST API client
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### "Authentication was cancelled or failed"
|
||
|
||
- Ensure the server URL is correct (including `https://`).
|
||
- Verify the server is reachable from your device's network.
|
||
- Confirm that `AUTH_ENABLED=True` on the server.
|
||
|
||
### Push notifications not arriving
|
||
|
||
1. Check that the app has notification permission (Settings → DocuElevate → Notifications).
|
||
2. Verify the device is registered: `GET /api/mobile/devices`.
|
||
3. Ensure the server can reach `https://exp.host` (outbound HTTPS on port 443).
|
||
4. On Android, add `google-services.json` to the `mobile/` directory if you are building your own binary.
|
||
|
||
### "Connection refused" or timeout
|
||
|
||
- Verify that the DocuElevate server is running and accessible.
|
||
- Ensure the server's `EXTERNAL_HOSTNAME` or reverse proxy is configured correctly.
|
||
- Check that the server accepts CORS requests from `docuelevate://`.
|
||
|
||
## Related Documentation
|
||
|
||
- [API Documentation](./API.md)
|
||
- [Configuration Guide](./ConfigurationGuide.md)
|
||
- [Deployment Guide](./DeploymentGuide.md)
|