Skip to Content
MobileArchitecture

Architecture

Navigation is React Navigation, driven by route constants in src/navigation/constants.js (SCREENS, STACK_NAMES, TAB_NAMES). The root is a native stack (RootNavigator) that switches between the auth flow, the main tab app, and a set of blocking “required action” screens:

RootNavigator (stack, initial = AuthStack) ├─ AuthStack login / signup / recover / onboarding ├─ PaymentRequiredScreen blocks app until membership payment is resolved ├─ CurrencyInactiveScreen blocks app until flyer currency is restored ├─ TwoFaRequiredScreen staff who must enable 2FA (defers to web portal) ├─ ForceUpdateScreen minimum supported build gate └─ HomeScreen → TabNavigator ├─ Home ├─ Chart ├─ Logbook ├─ Profile └─ Support

The required-action screens are full-screen gates shown instead of the tab app. Which one (if any) appears is decided once, post-login, by the auth context (see below).

Auth context & session lifecycle

src/context/AuthContext.js owns session state and is the single source of truth for “is the user signed in, and can they use the app”.

  • Token storage — the JWT is persisted via AsyncData (AsyncStorage). On cold start, checkAuthStatus() loads it and, if present, calls loadUserData().
  • loadUserData() fetches the member profile and runs validateUserStatus(), which returns a requiredAction: paymentRequiredcurrencyInactivetwoFaRequired → otherwise null (full access). useLogin.getTargetScreen() maps that action to the matching required-action screen.
  • Inactivity timeout — an in-app timer (default 30 min, via INACTIVITY_TIMEOUT_MINUTES) logs the user out after inactivity; it pauses in the background and resets on resume.
  • Logout has two modesexplicit: true (user-initiated) clears biometric credentials; explicit: false (inactivity or a 401) preserves them so the user can re-auth with Face/Touch ID / fingerprint.

Services layer

src/services/ wraps all backend and device access:

  • ApiServices — the axios client. Response interceptor refreshes the JWT from a token response header, triggers a full logout on 401 (via authService), retries on network/5xx errors (not 401/403), and keeps expected 4xxs out of Sentry.
  • authService — a tiny indirection so ApiServices can call AuthContext.logout() on a 401 without a circular import.
  • biometricServicereact-native-keychain wrapper (save / retrieve / clear credentials under BIOMETRY_ANY). Self-heals when the stored ciphertext can’t be decrypted (Keystore key invalidated) by clearing the entry and falling back to password. See Authentication.
  • PushNotificationService — FCM token + permissions + handlers. See Push notifications.
  • userService, notificationService, logbookService, … — typed wrappers over API endpoints.

Observability

src/utils/logger.js is the logging façade and the only thing that should talk to Sentry:

  • logger.debug — dev console only.
  • logger.info / logger.warn — Sentry breadcrumbs (no event).
  • logger.error — a Sentry exception (captureException).

So the rule of thumb is: an expected failure (a cancelled biometric prompt, a transient network error, a session-expiry 401) should be warn/debug, and only genuinely unexpected failures should be error. config/sentry.js additionally drops transient/connectivity noise in beforeSend. See Build & release → Source maps.

Last updated on