# DocuElevate Mobile App Native mobile application for DocuElevate, built with **React Native** and **Expo** for iOS, Android, and Web. ## Features - πŸ” **SSO Login** – authenticate via your DocuElevate server's OAuth2/SSO provider; an API token is auto-generated and stored securely in the device keychain - πŸ“· **Camera Capture** – scan documents directly with the device camera - πŸ–ΌοΈ **Photo Library** – select existing photos from the device's photo library for upload - πŸ“„ **File Picker** – upload PDFs, images, and Office documents from the device's Files app - πŸ”— **Share Extension** – send files from any app directly to DocuElevate via the iOS/Android share sheet - πŸ”” **Push Notifications** – receive real-time push notifications when documents finish processing (via Expo push notifications) - πŸ“‚ **Document List** – browse and search your processed documents - πŸ‘€ **Profile** – view account details and sign out - 🌐 **Web** – run directly in the browser via Expo web (Metro bundler) ## Requirements - Node.js 20.19.4+ (use [nvm](https://github.com/nvm-sh/nvm): `nvm use` in this directory) - Expo CLI (`npm install -g @expo/cli`) - Expo Go app on device (for development) **or** Expo Application Services (EAS) for production builds - An Expo account: ## Setup ```bash # 1. Install dependencies cd mobile npm install # 2. Start the development server (choose a platform) npx expo start # interactive menu (iOS / Android / Web) npx expo start --ios # open directly in iOS Simulator npx expo start --android # open in Android Emulator npx expo start --web # open in the browser ``` Scan the QR code with **Expo Go** on your iOS or Android device, or press `w` in the interactive menu to open the web build. ## Building DocuElevate uses **EAS Build** for production binaries. ```bash # Install EAS CLI npm install -g eas-cli # Log in to Expo eas login # Build for iOS eas build --platform ios # Build for Android eas build --platform android # Build for both eas build --platform all ``` > **Note:** The EAS project ID is already configured in `app.json` (`extra.eas.projectId`). You only need to run `eas init` if you are setting up a fork or a brand-new EAS project β€” in that case, replace the `extra.eas.projectId` value in `app.json` with the ID printed by `eas init`. ### iOS-specific - An Apple Developer account is required for TestFlight and App Store distribution - Update `eas.json` β†’ `submit.production.ios` with: - `appleId`: your Apple ID email address - `ascAppId`: App Store Connect β†’ App Information β†’ Apple ID - `appleTeamId`: Apple Developer portal β†’ Membership β†’ Team ID - Camera, photo library, and push notification usage descriptions are configured in `app.json` ### Android-specific - **Android push notifications** require a `google-services.json` file from Firebase Console. This file is intentionally excluded from the repository (`.gitignore`). To enable FCM push notifications in your Android builds: 1. Create a Firebase project at 2. Add an Android app with the package name `org.docuelevate.mobile` 3. Download `google-services.json` and place it in the `mobile/` directory 4. Add `"googleServicesFile": "./google-services.json"` back to the `android` section of `app.json` before building - The app runs and bundles correctly without `google-services.json`; only Android push notifications will be unavailable - For Play Store submission: create a service account in Google Play Console, download the JSON key as `google-play-service-account.json`, and update `eas.json` ## CI/CD An EAS Cloud Workflow (`mobile/.eas/workflows/create-builds.yml`) runs automatically when changes inside `mobile/` are pushed to `main`: 1. **Path filtering** β€” only commits that modify files under `mobile/` trigger a build; backend-only changes are skipped. 2. **Parallel builds** β€” iOS and Android production builds run at the same time on EAS Build. 3. **Auto-submit to Apple** β€” after the iOS build succeeds, the workflow submits the binary to App Store Connect (TestFlight) using the credentials in `eas.json` β†’ `submit.production.ios`. > An [App Store Connect API Key](https://docs.expo.dev/app-signing/app-credentials/#app-store-connect-api-key) must be configured in EAS (`eas credentials`) for non-interactive submission. ### Version management Build numbers (`ios.buildNumber` / `android.versionCode`) are managed **remotely** by EAS β€” see `eas.json`: - `"appVersionSource": "remote"` β€” EAS tracks the current build number on its servers, so each CI build automatically receives a unique, incrementing number without committing changes back to the repo. - `"autoIncrement": true` (production profile) β€” EAS bumps the build number before every production build. The values in `app.json` are used as the **initial seed** when the remote version is first created; after that they are informational only. Use `eas build:version:get` / `eas build:version:set` to inspect or override the remote version. ## Configuration No code changes are needed to point the app at a different server. The server URL is entered by the user on the login screen and stored in the device's secure store. ## Authentication Flow 1. User enters the DocuElevate server URL on the login screen 2. The app opens the server's `/login?mobile=1&redirect_uri=docuelevate://callback` URL in the system browser 3. The user authenticates (SSO / local login) 4. The server redirects back to `docuelevate://callback` 5. The app exchanges the browser session for a permanent API token via `POST /api/mobile/generate-token` 6. The token is stored in the device's secure keychain (`expo-secure-store`) ## Push Notifications The app uses **Expo Push Notifications** which route through Expo's servers to APNs (iOS) and FCM (Android) – no server-side APNs/FCM credentials are needed. The Expo push token is sent to the backend after login via `POST /api/mobile/register-device` and the server uses it to deliver notifications when documents are processed. ## Project Structure ``` mobile/ β”œβ”€β”€ app/ # expo-router file-based routes β”‚ β”œβ”€β”€ _layout.tsx # Root layout (AuthProvider + auth guard) β”‚ β”œβ”€β”€ (auth)/ # Unauthenticated route group β”‚ β”‚ β”œβ”€β”€ _layout.tsx # Auth stack (no header) β”‚ β”‚ β”œβ”€β”€ index.tsx # Welcome screen β”‚ β”‚ └── login.tsx # Login screen β”‚ └── (tabs)/ # Authenticated route group β”‚ β”œβ”€β”€ _layout.tsx # Tab navigator (Upload / Files / Profile) β”‚ β”œβ”€β”€ index.tsx # Upload tab β”‚ β”œβ”€β”€ files.tsx # Files tab β”‚ └── profile.tsx # Profile tab β”œβ”€β”€ App.tsx # Legacy file (not the entry point; see app/) β”œβ”€β”€ app.json # Expo configuration β”œβ”€β”€ eas.json # EAS Build configuration β”œβ”€β”€ package.json β”œβ”€β”€ tsconfig.json └── src/ β”œβ”€β”€ context/ β”‚ β”œβ”€β”€ AuthContext.tsx # Authentication state management β”‚ └── ShareContext.tsx # Shared-file queue (iOS Share Sheet / Android Intent) β”œβ”€β”€ hooks/ β”‚ └── usePushNotifications.ts # Push notification registration β”œβ”€β”€ screens/ β”‚ β”œβ”€β”€ WelcomeScreen.tsx # Branded intro / onboarding β”‚ β”œβ”€β”€ LoginScreen.tsx # SSO login β”‚ β”œβ”€β”€ UploadScreen.tsx # Camera capture + photo library + file picker β”‚ β”œβ”€β”€ FilesScreen.tsx # Document list β”‚ └── ProfileScreen.tsx # User profile + sign out └── services/ └── api.ts # DocuElevate API client ``` ## 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. ### iOS – how it works `app.json` declares `CFBundleDocumentTypes` in the iOS `infoPlist` section. This tells iOS which file types the app can receive, causing it to appear in the share sheet when the user shares a matching file. When the user taps **DocuElevate** in the share sheet, iOS passes the file path to the app via `application:openURL:options:`. The URL may arrive as a standard `file://` path or under the app's custom `docuelevate://` scheme. The root layout (`app/_layout.tsx`) listens for incoming URLs via `Linking.addEventListener` (warm start) and `Linking.getInitialURL()` (cold start). If the URL uses the `docuelevate://` scheme it is automatically rewritten to `file://` before being forwarded. Incoming files are stored in `ShareContext` and automatically uploaded by `UploadScreen`. #### Handling "unmatched route" errors from "Open In…" iOS sometimes delivers the file path under the `docuelevate://` scheme: ``` docuelevate://private/var/mobile/Library/Mobile Documents/…/Invoice.pdf ``` expo-router strips the scheme and tries to match `/private/var/mobile/…` as an in-app route. The catch-all `app/+not-found.tsx` intercepts this, detects the filesystem-path pattern, adds the file directly to `ShareContext`, and redirects to the Upload tab. `UploadScreen` picks up the pending file and begins uploading automatically. The `Linking` listener in the root layout may also fire for the same URL; `ShareContext` deduplicates by URI to prevent double uploads. **Supported iOS file types:** PDF, images (JPEG / PNG / GIF / BMP / TIFF / WebP), plain text, Word (`.docx`, `.doc`), Excel (`.xlsx`, `.xls`), PowerPoint (`.pptx`, `.ppt`), and any other file (`public.data`). To use the share sheet: 1. Ensure the app is installed on the device. 2. Open any supported file in Files, Mail, Safari, etc. 3. Tap the **Share** button β†’ find **DocuElevate** in the share sheet. 4. The file is uploaded immediately. > **Note:** `CFBundleDocumentTypes` with `LSHandlerRank: Alternate` means DocuElevate appears in the share sheet as an option but does **not** become the default app for any file type. #### iOS Action Extension (future enhancement) Apps like DeepL ("Translate in DeepL") appear as **Action Extensions** in the iOS share sheet, which requires a separate Xcode target and native Swift code. This is planned as a future enhancement. The current `CFBundleDocumentTypes` approach places DocuElevate in the "Open With" row of the share sheet. ### Android – how it works `app.json` declares `intentFilters` for `ACTION_SEND` and `ACTION_SEND_MULTIPLE` with `mimeType: "*/*"`. When a user shares a file from another app and selects DocuElevate, Android delivers the content URI through the share intent, which is captured via `Linking.getInitialURL()` and processed the same way as on iOS. ## Backend API The mobile app uses the following backend endpoints: | Method | Endpoint | Description | |----------|-------------------------------------|---------------------------------------| | `POST` | `/api/mobile/generate-token` | Exchange SSO session for API token | | `POST` | `/api/mobile/register-device` | Register Expo push token | | `GET` | `/api/mobile/devices` | List registered devices | | `DELETE` | `/api/mobile/devices/{id}` | Deactivate device registration | | `GET` | `/api/mobile/whoami` | Get current user profile | | `POST` | `/api/ui-upload` | Upload file for processing | | `GET` | `/api/files` | List processed documents | | `GET` | `/api/files/{id}` | Get processing status of a single file | Authentication uses `Authorization: Bearer ` on all requests. ## Troubleshooting ### `Session expired Local session` when running `eas build` EAS CLI stores an Apple ID session locally to manage provisioning profiles and code-signing certificates. This session expires after several weeks. **Quick fix (local):** Re-authenticate by running: ```bash eas credentials ``` Select iOS β†’ re-enter your Apple ID credentials when prompted. **Recommended (CI / automation):** Switch to an [App Store Connect API Key](https://docs.expo.dev/app-signing/app-credentials/#app-store-connect-api-key) which does not expire automatically and works non-interactively: 1. Go to [appstoreconnect.apple.com β†’ Users β†’ Integrations β†’ Keys](https://appstoreconnect.apple.com/access/integrations/api) and create a key with *Developer* or *App Manager* role. 2. Download the `.p8` file; note the **Key ID** and **Issuer ID**. 3. Run `eas credentials` β†’ iOS β†’ *Add an App Store Connect API key* and upload the `.p8` file. Once an API key is stored in EAS, all future builds (local and CI) will use it automatically without requiring an Apple ID session. ### `[DEP0169] DeprecationWarning: url.parse()` during build ```text (node:XXXXX) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized… ``` This is emitted by EAS CLI itself when it runs on **Node.js 22 or later**, which deprecates `url.parse()`. It is a warning only and does not cause build failures on its own. The `eas.json` build profiles already suppress it via `"NODE_NO_WARNINGS": "1"` in their `env` sections. To suppress the warning locally, either: ```bash # Option A: Run with the warning suppressed NODE_NO_WARNINGS=1 eas build --platform ios # Option B: Switch to the pinned Node version (no warning on Node 20) nvm use # reads .nvmrc β†’ Node 20.19.4 eas build --platform ios ```