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.jsonTech Stack
| Technology | Version | Purpose |
|---|---|---|
| Expo | 52 | React Native framework |
| React Native | 0.76 | Cross-platform UI |
| TypeScript | 5.3 | Type safety |
| Expo Router | 4.0 | File-based routing |
| Supabase | 2.90 | Backend services |
| Zod | 3.23 | Runtime validation |
Prerequisites
Before starting development:
-
Node.js 18+
node --version # v18.x or higher -
npm or pnpm
npm --version # 9.x or higher -
Expo CLI
npm install -g expo-cli -
iOS Development (macOS only)
- Xcode 15+
- iOS Simulator
- CocoaPods
-
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/mobile2. 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=falseNever 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 EmulatorRunning 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
- Install Expo Go from App Store / Play Store
- Scan the QR code from
npm start - App loads on your device
VPN functionality requires native builds and will not work in Expo Go.
Building for Production
EAS Build Setup
-
Install EAS CLI
npm install -g eas-cli -
Login to Expo
eas login -
Configure Project
eas build:configure
Building iOS
# Development build (for testing)
npm run build:ios -- --profile development
# Production build
npm run build:ios -- --profile productionBuilding 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 androidBuild 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:coverageTest Structure
src/
├── __tests__/
│ ├── mocks/
│ │ └── react-native.ts
│ └── setup.ts
└── lib/
└── __tests__/
├── storage.test.ts
├── validation.test.ts
└── auth-context.test.tsxExample 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 typecheckLinting
# Run ESLint
npm run lint
# Fix auto-fixable issues
npm run lint -- --fixESLint 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.mFor Android (Kotlin):
android/
└── app/src/main/java/io/priv/mobile/
└── wireguard/
└── WireGuardModule.ktContributing
Development Workflow
-
Create Feature Branch
git checkout -b feature/my-feature -
Make Changes
- Write code
- Add tests
- Update types
-
Verify Quality
npm run typecheck npm run lint npm test -
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 flowDebugging
React Native Debugger
- Shake device or Cmd+D (iOS) / Cmd+M (Android)
- Select "Debug Remote JS"
- Open Chrome DevTools
Flipper
For advanced debugging:
- Install Flipper
- Run the app in development
- 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
- VPN Documentation - Understand VPN architecture
- API Reference - Backend API documentation
- SDK Guide - Integrate with PRIV SDK