Skip to main content

Mobile App

The Bellamy Book mobile app is a thin native shell (Expo / React Native) that loads your self-hosted frontend in a WebView. Users enter the frontend URL once; the app saves it and opens it on every launch. All API calls go from the frontend inside the WebView; the app does not talk to the backend directly. The app also obtains an Expo push token and injects it into the WebView so the frontend can register with the API for chat and call notifications.


Overview

AreaStatusNotes
URL entry (first launch)✅ DoneEnter frontend URL, validation, save to AsyncStorage
WebView (main app)✅ DoneLoads saved URL; chat, calls, media work inside WebView
Settings✅ DoneChange URL, remove URL, back to app (gear in header)
Persistence✅ DoneAsyncStorage key @BellamyBook:webAppUrl
Expo push (chat/call)✅ ReadyToken registration + worker delivery; user gets notifications when app is in background
Deep linking from push📋 LaterOpen app and navigate to chat/call when user taps notification
EAS Build (IPA/APK)📋 LaterProduction binaries and store submission

Stack: Expo ~54, React 19, React Native 0.81, react-native-webview, @react-native-async-storage/async-storage, expo-notifications, expo-device.


Architecture

User device


┌─────────────────────────────────────┐
│ Bellamy Book Mobile (Expo / RN) │
│ • URL entry (first time / after │
│ “Remove URL”) │
│ • WebView → your frontend URL │
│ • Settings (change/remove URL) │
│ • Expo push token → inject into │
│ WebView for API registration │
└─────────────────────────────────────┘

│ Loads

┌─────────────────────────────────────┐
│ Self-hosted frontend │
│ (e.g. https://app.yoursite.com) │
│ • Same React app as desktop │
│ • Chat, calls, feed in WebView │
│ • Listens for EXPO_PUSH_TOKEN and │
│ registers with API │
└─────────────────────────────────────┘

The mobile app does not call the backend API directly; all API calls (auth, chat, push subscription, etc.) are made by the frontend running inside the WebView. The app only stores the frontend URL and provides the Expo push token to the WebView.


Project Structure

The mobile app lives under Src/mobile/:

Src/mobile/
├── App.js # Root: load URL from storage → Entry | WebView | Settings
├── index.js # Expo entry
├── app.json # Expo config (name, slug, icons, scheme)
├── metro.config.js # Metro config
├── polyfill-node18.js # Array#toReversed for Node 18 (used by start script)
├── package.json
├── assets/ # Icons, splash (logo.svg; PNGs generated from it)
└── src/
├── constants/storageKeys.js # AsyncStorage key(s)
├── services/
│ ├── urlStorage.js # get/set/remove saved URL
│ └── pushNotifications.js # permission + Expo push token
├── theme/colors.js
├── utils/urlValidation.js # Normalize & validate URL
└── screens/
├── UrlEntryScreen.js # First-time / change URL form
├── WebViewScreen.js # WebView + header + settings; injects push token
└── SettingsScreen.js # Show URL, Change, Remove, Back

Expo Push Integration

  1. On WebView mount, the app calls registerForPushNotificationsAsync() (in src/services/pushNotifications.js) to request notification permission and obtain an Expo push token.
  2. When the token is available, WebViewScreen injects it into the WebView via window.postMessage({ type: 'EXPO_PUSH_TOKEN', token: '...' }, '*').
  3. The frontend (in the browser or inside the WebView) listens for this message and registers the token with the API (POST /api/push-subscriptions/expo) so the backend can send chat and call notifications to this device.
  4. The ExpoPushNotificationWorker consumes RabbitMQ messages and sends push via Expo’s service when someone sends a chat message or starts a call.

Expo push is ready: token registration and delivery via ExpoPushNotificationWorker are implemented; users receive chat and call notifications when the app is in the background. For full flow and API details, see Web Push and Expo Push. Later: deep linking when the user taps a notification (e.g. open app and go to the conversation or call screen) and EAS Build for production IPA/APK.


Run & Build

Prerequisites

  • Node 18+ (Node 20+ recommended; Node 18 needs the polyfill applied by the start script)
  • npm or yarn
  • Expo Go on device, or iOS/Android simulator

Install and start

cd Src/mobile
npm install
npm start
  • Node 18: The npm start script preloads polyfill-node18.js so Metro works (toReversed polyfill).
  • Choose port if prompted (e.g. 8083 when 8081 is in use).
  • Scan QR with Expo Go, or press i / a for iOS/Android simulator.

First launch

  1. App shows URL entry.
  2. Enter your Bellamy Book frontend URL (e.g. https://your-app.example.com or http://localhost:5173 for dev).
  3. Tap Continue → URL is saved and WebView loads.
  4. Use the app; tap ⚙️ in the header to open Settings (change URL, remove URL, or go back to app).

Other scripts

ScriptCommandDescription
iOSnpm run iosStart and open iOS simulator
Androidnpm run androidStart and open Android emulator
Webnpm run webStart for web (Expo web)
Iconsnpm run generate-iconsRegenerate app icon/splash from assets/logo.svg (same as frontend logo)

Configuration

  • Frontend URL: Stored in AsyncStorage (@BellamyBook:webAppUrl). Set on first launch or in Settings.
  • Expo push: Requires EXPO_PUBLIC_PROJECT_ID in app config (e.g. app.json / EAS) for expo-notifications to resolve the push token. For local dev with Expo Go, the default project is used.

Troubleshooting

  • Port in use: When starting, choose “yes” to use another port (e.g. 8083).
  • configs.toReversed is not a function: Use npm start (which preloads the Node 18 polyfill), or upgrade to Node 20+.
  • WebView “Connection Error”: Check URL, network, and that the frontend is reachable (HTTPS in production; for local dev you may need http:// and correct host, e.g. machine IP or localhost depending on device).
  • Expo push token not received in frontend: Ensure the WebView has loaded and the frontend listens for EXPO_PUSH_TOKEN via window.addEventListener('message', ...). The app injects the token after the WebView loads; if the user denies notification permission, no token is sent.