PRIV ProtocolPRIV Docs
Mobile App

Development Guide

Developer guide for the PRIV mobile app - project structure, setup, building, and testing.

Development Guide

This guide covers setting up the PRIV mobile app for local development, understanding the project structure, and contributing to the codebase.


Project Structure

The mobile app is located at apps/mobile/ in the monorepo:

apps/mobile/
├── src/
│   ├── app/                 # Expo Router screens
│   │   ├── (auth)/          # Auth screens (sign-in, sign-up)
│   │   ├── (tabs)/          # Main tab screens
│   │   │   ├── index.tsx    # Home tab
│   │   │   ├── vpn.tsx      # VPN tab
│   │   │   ├── earnings.tsx # Earnings tab
│   │   │   ├── stats.tsx    # Stats tab
│   │   │   └── settings.tsx # Settings tab
│   │   ├── _layout.tsx      # Root layout
│   │   ├── onboarding.tsx   # Onboarding flow
│   │   └── wallet-connect.tsx
│   ├── components/          # Reusable components
│   │   ├── Button.tsx
│   │   ├── LoadingSpinner.tsx
│   │   ├── ErrorState.tsx
│   │   ├── EmptyState.tsx
│   │   └── Toast.tsx
│   ├── hooks/               # Custom React hooks
│   │   ├── useVpn.ts
│   │   ├── useVpnSession.ts
│   │   ├── useEarnings.ts
│   │   ├── useWallet.ts
│   │   └── useHaptics.ts
│   ├── lib/                 # Utilities and services
│   │   ├── supabase.ts      # Supabase client
│   │   ├── auth-context.tsx # Auth provider
│   │   ├── api-client.ts    # Secure fetch wrapper
│   │   ├── analytics.ts     # Analytics module
│   │   ├── storage.ts       # Secure storage
│   │   └── validation.ts    # Zod schemas
│   ├── services/            # Business logic
│   │   ├── vpn-service.ts
│   │   └── server-selection.ts
│   ├── contexts/            # React contexts
│   │   └── VpnContext.tsx
│   ├── native/              # Native module bridges
│   │   └── WireGuardModule.ts
│   ├── types/               # TypeScript types
│   │   ├── vpn.ts
│   │   └── security.ts
│   └── constants/           # App constants
│       └── colors.ts
├── assets/                  # Images, fonts
├── app.json                 # Expo configuration
├── eas.json                 # EAS Build configuration
├── package.json
└── tsconfig.json

Tech Stack

TechnologyVersionPurpose
Expo52React Native framework
React Native0.76Cross-platform UI
TypeScript5.3Type safety
Expo Router4.0File-based routing
Supabase2.90Backend services
Zod3.23Runtime validation

Prerequisites

Before starting development:

  1. Node.js 18+

    node --version  # v18.x or higher
  2. npm or pnpm

    npm --version   # 9.x or higher
  3. Expo CLI

    npm install -g expo-cli
  4. iOS Development (macOS only)

    • Xcode 15+
    • iOS Simulator
    • CocoaPods
  5. Android Development

    • Android Studio
    • Android SDK
    • Android Emulator or device

Development Setup

1. Clone and Install

# Clone the monorepo
git clone https://github.com/priv-protocol/priv-protocol.git
cd priv-protocol

# Install all dependencies
npm install

# Navigate to mobile app
cd apps/mobile

2. Environment Variables

Create a .env file in apps/mobile/:

# Supabase
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# API
EXPO_PUBLIC_API_URL=https://api.priv.io

# Feature Flags
EXPO_PUBLIC_ENABLE_ANALYTICS=true
EXPO_PUBLIC_ENABLE_VPN=true

# Debug
EXPO_PUBLIC_DEBUG_MODE=false

Never commit .env files. Use .env.example as a template.

3. Start Development Server

# Start Expo development server
npm start

# Or with specific platform
npm run ios     # iOS Simulator
npm run android # Android Emulator

Running on Simulators

iOS Simulator

# Start iOS simulator
npm run ios

# Or press 'i' after 'npm start'

Requirements:

  • macOS with Xcode installed
  • iOS Simulator from Xcode
  • CocoaPods for native dependencies

Android Emulator

# Start Android emulator
npm run android

# Or press 'a' after 'npm start'

Requirements:

  • Android Studio installed
  • Android SDK configured
  • Emulator created in AVD Manager

Physical Device

  1. Install Expo Go from App Store / Play Store
  2. Scan the QR code from npm start
  3. App loads on your device

VPN functionality requires native builds and will not work in Expo Go.


Building for Production

EAS Build Setup

  1. Install EAS CLI

    npm install -g eas-cli
  2. Login to Expo

    eas login
  3. Configure Project

    eas build:configure

Building iOS

# Development build (for testing)
npm run build:ios -- --profile development

# Production build
npm run build:ios -- --profile production

Building Android

# Development build
npm run build:android -- --profile development

# Production build (APK)
npm run build:android -- --profile production

# Production build (AAB for Play Store)
npm run build:android -- --profile production --platform android

Build Profiles

Configure in eas.json:

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal"
    },
    "production": {
      "autoIncrement": true
    }
  }
}

Testing

Running Tests

# Run all tests
npm test

# Watch mode
npm run test:watch

# Coverage report
npm run test:coverage

Test Structure

src/
├── __tests__/
│   ├── mocks/
│   │   └── react-native.ts
│   └── setup.ts
└── lib/
    └── __tests__/
        ├── storage.test.ts
        ├── validation.test.ts
        └── auth-context.test.tsx

Example Test

// src/lib/__tests__/validation.test.ts
import { emailSchema, passwordSchema } from '../validation';

describe('Validation Schemas', () => {
  describe('emailSchema', () => {
    it('accepts valid email', () => {
      const result = emailSchema.safeParse('user@example.com');
      expect(result.success).toBe(true);
    });

    it('rejects invalid email', () => {
      const result = emailSchema.safeParse('invalid');
      expect(result.success).toBe(false);
    });
  });

  describe('passwordSchema', () => {
    it('requires minimum length', () => {
      const result = passwordSchema.safeParse('short');
      expect(result.success).toBe(false);
    });

    it('requires uppercase', () => {
      const result = passwordSchema.safeParse('alllowercase1!');
      expect(result.success).toBe(false);
    });
  });
});

Testing Components

// src/components/__tests__/Button.test.tsx
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from '../Button';

describe('Button', () => {
  it('renders correctly', () => {
    const { getByText } = render(
      <Button onPress={() => {}}>Press Me</Button>
    );
    expect(getByText('Press Me')).toBeTruthy();
  });

  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByText } = render(
      <Button onPress={onPress}>Press Me</Button>
    );
    fireEvent.press(getByText('Press Me'));
    expect(onPress).toHaveBeenCalled();
  });
});

Code Quality

Type Checking

# Run TypeScript compiler
npm run typecheck

Linting

# Run ESLint
npm run lint

# Fix auto-fixable issues
npm run lint -- --fix

ESLint Configuration

The project uses eslint-config-expo:

{
  "extends": "expo",
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/explicit-function-return-type": "warn"
  }
}

Key Modules

Authentication (auth-context.tsx)

Manages user authentication state:

interface AuthContextType {
  session: Session | null
  user: User | null
  isLoading: boolean
  signIn: (email: string, password: string) => Promise<void>
  signUp: (email: string, password: string) => Promise<void>
  signOut: () => Promise<void>
}

VPN Service (vpn-service.ts)

Handles VPN connection logic:

interface VpnService {
  connect(serverId?: string): Promise<void>
  disconnect(): Promise<void>
  getStatus(): VpnStatus
  getStatistics(): VpnStatistics
}

Analytics (analytics.ts)

Tracks events and sessions:

// Track custom event
await trackEvent('vpn_connect', 'VPN Connected', {
  server_id: 'us-east-1',
  connection_time_ms: 1250
})

// Track screen view
await trackScreenView('Earnings')

// Track error
await trackError(new Error('Connection failed'))

Secure Storage (storage.ts)

Encrypted key-value storage:

// Store sensitive data
await setSecureItem('auth_token', token)

// Retrieve data
const token = await getSecureItem('auth_token')

// Store JSON with validation
await setJSON('user_prefs', prefs, userPrefsSchema)

Native Modules

WireGuard Module

The VPN uses native WireGuard implementations:

// src/native/WireGuardModule.ts
interface WireGuardModule {
  connect(config: VpnConfig): Promise<void>
  disconnect(): Promise<void>
  getStatus(): Promise<NativeVpnStatus>
  getStatistics(): Promise<NativeVpnStatistics>
  generateKeyPair(): Promise<{ privateKey: string; publicKey: string }>
}

Adding Native Code

For iOS (Swift):

ios/
└── Modules/
    └── WireGuard/
        ├── WireGuardModule.swift
        └── WireGuardModule.m

For Android (Kotlin):

android/
└── app/src/main/java/io/priv/mobile/
    └── wireguard/
        └── WireGuardModule.kt

Contributing

Development Workflow

  1. Create Feature Branch

    git checkout -b feature/my-feature
  2. Make Changes

    • Write code
    • Add tests
    • Update types
  3. Verify Quality

    npm run typecheck
    npm run lint
    npm test
  4. Submit PR

    • Clear description
    • Link related issues
    • Request review

Code Style

  • Use functional components with hooks
  • Type all props and state
  • Validate inputs with Zod
  • Use meaningful variable names
  • Comment complex logic

Commit Messages

Follow conventional commits:

feat: add VPN auto-reconnect
fix: resolve earnings calculation bug
docs: update development guide
test: add storage tests
refactor: simplify auth flow

Debugging

React Native Debugger

  1. Shake device or Cmd+D (iOS) / Cmd+M (Android)
  2. Select "Debug Remote JS"
  3. Open Chrome DevTools

Flipper

For advanced debugging:

  1. Install Flipper
  2. Run the app in development
  3. Flipper auto-connects

Console Logging

if (__DEV__) {
  console.log('[VPN] Connection status:', status)
}

Resources


Getting Help

  • GitHub Issues: Report bugs and request features
  • Discord: Join our developer community
  • Email: dev@priv.io

Next Steps