Skip to content

Latest commit

 

History

History
841 lines (723 loc) · 23.4 KB

File metadata and controls

841 lines (723 loc) · 23.4 KB

🎯 Usage Examples

Complete integration examples for React Native OpenVPN
Production-ready code samples and patterns for secure VPN integration

🚀 Quick Navigation

Example Description Link
🔌 Basic Connection Simple connect/disconnect functionality View →
⚙️ Advanced Config Enterprise-grade configuration options View →
📦 State Management Minimal state management with Zustand View →
🚨 Error Handling Comprehensive error management View →

🔌 Basic VPN Connection

Simple and reliable VPN connection patterns

📱 Simple Connect/Disconnect

import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, Alert, StyleSheet } from 'react-native';
import * as OpenVPN from 'react-native-openvpn';

const SimpleVPN = () => {
  const [isConnected, setIsConnected] = useState(false);
  const [isConnecting, setIsConnecting] = useState(false);
  const [connectionState, setConnectionState] = useState(OpenVPN.ConnectionState.DISCONNECTED);

  useEffect(() => {
    // Set up state listener
    const subscription = OpenVPN.addOpenVPNStateChangeListener((state) => {
      setConnectionState(state.state);
      setIsConnected(state.state === OpenVPN.ConnectionState.CONNECTED);
      setIsConnecting(state.state === OpenVPN.ConnectionState.CONNECTING);
    });

    // Get initial state
    OpenVPN.requestCurrentState();

    return () => subscription.remove();
  }, []);

  const handleConnect = async () => {
    try {
      // Android permission check
      if (Platform.OS === 'android') {
        const isPrepared = await OpenVPN.isPrepared();
        if (!isPrepared) {
          const granted = await OpenVPN.prepare();
          if (!granted) {
            Alert.alert('Permission Required', 'VPN permission is required to continue');
            return;
          }
        }
      }

      await OpenVPN.connect({
        address: 'your-server.com',
        username: 'your-username',
        password: 'your-password',
        openVPNConfig: yourConfigString,
        
        iOSOptions: {
          localizedDescription: 'My VPN',
          networkExtensionBundleIdentifier: 'com.yourapp.vpn',
          disconnectOnSleep: false,
          onDemandEnabled: false,
        },
        
        androidOptions: {
          Notification: {
            openActivityPackageName: 'com.yourapp.MainActivity',
            titleNotification: 'VPN Connected',
            titleConnected: 'Secure connection active',
            titleConnecting: 'Establishing secure connection...',
            showDisconnectAction: true,
            titleDisconnectButton: 'Disconnect',
          },
        },
      });
    } catch (error) {
      Alert.alert('Connection Error', error.message);
    }
  };

  const handleDisconnect = async () => {
    try {
      await OpenVPN.disconnect();
    } catch (error) {
      Alert.alert('Disconnect Error', error.message);
    }
  };

  const getStatusColor = () => {
    switch (connectionState) {
      case OpenVPN.ConnectionState.CONNECTED: return '#4CAF50';
      case OpenVPN.ConnectionState.CONNECTING: return '#FF9800';
      case OpenVPN.ConnectionState.DISCONNECTING: return '#FF9800';
      case OpenVPN.ConnectionState.ERROR: return '#F44336';
      default: return '#9E9E9E';
    }
  };

  const getStatusText = () => {
    switch (connectionState) {
      case OpenVPN.ConnectionState.CONNECTED: return '🟢 Connected';
      case OpenVPN.ConnectionState.CONNECTING: return '🟡 Connecting...';
      case OpenVPN.ConnectionState.DISCONNECTING: return '🟡 Disconnecting...';
      case OpenVPN.ConnectionState.ERROR: return '🔴 Connection Error';
      default: return '⚫ Disconnected';
    }
  };

  return (
    <View style={styles.container}>
      <View style={styles.statusContainer}>
        <Text style={[styles.statusText, { color: getStatusColor() }]}>
          {getStatusText()}
        </Text>
      </View>

      <View style={styles.buttonContainer}>
        {!isConnected && !isConnecting && (
          <TouchableOpacity style={styles.connectButton} onPress={handleConnect}>
            <Text style={styles.buttonText}>Connect to VPN</Text>
          </TouchableOpacity>
        )}

        {isConnecting && (
          <TouchableOpacity style={styles.disabledButton} disabled>
            <Text style={styles.buttonText}>Connecting...</Text>
          </TouchableOpacity>
        )}

        {isConnected && (
          <TouchableOpacity style={styles.disconnectButton} onPress={handleDisconnect}>
            <Text style={styles.buttonText}>Disconnect VPN</Text>
          </TouchableOpacity>
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  statusContainer: {
    marginBottom: 40,
    padding: 20,
    borderRadius: 10,
    backgroundColor: '#f5f5f5',
  },
  statusText: {
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  buttonContainer: {
    width: '100%',
  },
  connectButton: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
  },
  disconnectButton: {
    backgroundColor: '#F44336',
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
  },
  disabledButton: {
    backgroundColor: '#CCCCCC',
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default SimpleVPN;

⚙️ Advanced Configuration

Enterprise-grade VPN configuration with advanced routing and security options

🏢 Enterprise VPN with Custom Routing

import React from 'react';
import * as OpenVPN from 'react-native-openvpn';

const EnterpriseVPN = () => {
  const connectToEnterprise = async () => {
    try {
      await OpenVPN.connect({
        address: 'enterprise-vpn.company.com',
        username: 'employee@company.com',
        password: 'secure-password',
        openVPNConfig: enterpriseConfig,
        
        iOSOptions: {
          localizedDescription: 'Company VPN - Secure Access',
          networkExtensionBundleIdentifier: 'com.company.app.vpn',
          disconnectOnSleep: true,
          onDemandEnabled: true,
          includeAllNetworks: false,
          excludeLocalNetworks: true,
          excludeCellularServices: false,
          persistTun: true,
          connectionTimeout: 30,
          googleDNSFallback: true,
          autologinSessions: false,
          retryOnAuthFailed: true,
        },
        
        androidOptions: {
          // Use OpenVPN 2.x for better compatibility
          useOpenVPN3: false,
          compatibilityMode: OpenVPN.AndroidCompatibilityMode.OpenVPN_2_5_x,
          
          // Network behavior
          useSystemProxy: true,
          useReconnectOnNetworkChange: true,
          usePauseOnScreenOff: false,
          useProfileEncryption: true,
          
          // Custom DNS configuration
          overrideDNS: true,
          DNS1: '8.8.8.8',
          DNS2: '1.1.1.1',
          searchDomain: 'company.local',
          
          // Advanced routing
          useDefaultRoute: false,
          customRoutes: '10.0.0.0/8 172.16.0.0/12 192.168.0.0/16',
          excludedRoutes: '192.168.1.0/24 10.0.1.0/24',
          allowLocalLAN: true,
          blockUnusedAddressFamilies: true,
          ignorePushedRoutes: false,
          
          // IPv6 support
          useDefaultRouteV6: false,
          customRoutesV6: 'fd00::/8',
          excludedRoutesV6: 'fe80::/10',
          
          // App-specific routing
          allowedVPNApps: [
            'com.company.secure-app',
            'com.company.internal-tools',
          ],
          allowedVPNAppsAreDisallowed: false,
          allowAppVpnBypass: false,
          
          // Security settings
          tlsProfileSecurity: OpenVPN.TLSSecurityProfile.PREFERRED,
          expectServerTLSCert: true,
          certificateHostnameCheck: true,
          remoteCertificateSubject: 'CN=company-vpn-server',
          useTLSAuth: true,
          dataCiphers: 'AES-256-GCM:AES-128-GCM:AES-256-CBC',
          packetDigests: 'SHA256:SHA1',
          
          // Client behavior
          persistTun: true,
          pushPeerInfo: true,
          useRandomHostname: false,
          useFloat: false,
          
          // Custom OpenVPN options
          useCustomConfig: true,
          customOptions: [
            'verb 3',
            'mute 20',
            'keepalive 10 60',
            'ping-timer-rem',
            'persist-tun',
            'persist-key',
          ].join('\n'),
          
          // Reconnection strategy
          connectRetryMax: '10',
          connectRetryMaxTime: '300',
          connectRetry: '5',
          
          // Rich notification
          Notification: {
            openActivityPackageName: 'com.company.app.MainActivity',
            titleNotification: 'Company VPN',
            titleConnected: '🏢 Connected to Company Network',
            titleConnecting: '🔄 Connecting to Corporate VPN...',
            titleDisconnecting: '🔄 Disconnecting from VPN...',
            titleDisconnected: '🔴 Disconnected from Company VPN',
            titleError: '❌ VPN Connection Error',
            showDisconnectAction: true,
            titleDisconnectButton: 'Disconnect VPN',
            showTimer: true,
          },
        },
      });
      
      console.log('Enterprise VPN connected successfully');
    } catch (error) {
      console.error('Enterprise VPN connection failed:', error);
      throw error;
    }
  };

  return null; // Your UI implementation
};

📦 State Management

Minimal and efficient VPN state management with Zustand

🗃️ Zustand VPN Store

import { create } from 'zustand';
import * as OpenVPN from 'react-native-openvpn';

// 🏷️ Define VPN store state interface
interface VPNState {
  // Connection state
  connectionState: OpenVPN.ConnectionState;
  isConnected: boolean;
  isConnecting: boolean;
  
  // Connection details
  serverAddress: string | null;
  lastError: string | null;
  
  // Actions
  setConnectionState: (state: OpenVPN.ConnectionState) => void;
  setServerAddress: (address: string | null) => void;
  setError: (error: string | null) => void;
  clearError: () => void;
  reset: () => void;
}

// 🎯 Create Zustand store
export const useVPNStore = create<VPNState>((set, get) => ({
  // Initial state
  connectionState: OpenVPN.ConnectionState.DISCONNECTED,
  isConnected: false,
  isConnecting: false,
  serverAddress: null,
  lastError: null,

  // Actions
  setConnectionState: (state: OpenVPN.ConnectionState) => set((prev) => {
    const isConnected = state === OpenVPN.ConnectionState.CONNECTED;
    const isConnecting = state === OpenVPN.ConnectionState.CONNECTING;
    
    return {
      connectionState: state,
      isConnected,
      isConnecting,
      lastError: null, // Clear error on state change
    };
  }),

  setServerAddress: (address: string | null) => set({ serverAddress: address }),

  setError: (error: string | null) => set({ 
    lastError: error,
    connectionState: error ? OpenVPN.ConnectionState.ERROR : get().connectionState 
  }),

  clearError: () => set({ lastError: null }),

  reset: () => set({
    connectionState: OpenVPN.ConnectionState.DISCONNECTED,
    isConnected: false,
    isConnecting: false,
    serverAddress: null,
    lastError: null,
  }),
}));

📱 VPN Connection Page

import React, { useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Platform } from 'react-native';
import * as OpenVPN from 'react-native-openvpn';
import { useVPNStore } from './vpn-store';

const VPNConnectionPage: React.FC = () => {
  const {
    connectionState,
    isConnected,
    isConnecting,
    serverAddress,
    lastError,
    setConnectionState,
    setServerAddress,
    setError,
    clearError,
  } = useVPNStore();

  // 🔗 Set up OpenVPN event listeners
  useEffect(() => {
    const subscription = OpenVPN.addOpenVPNStateChangeListener((event) => {
      setConnectionState(event.state);
    });

    // 🔄 Request initial state
    OpenVPN.requestCurrentState();

    return () => subscription.remove();
  }, [setConnectionState]);

  // 🔌 Connect function
  const handleConnect = async () => {
    try {
      clearError();
      setServerAddress('your-server.com');
      
      // Android permission check
      if (Platform.OS === 'android') {
        const isPrepared = await OpenVPN.isPrepared();
        if (!isPrepared) {
          const granted = await OpenVPN.prepare();
          if (!granted) {
            throw new Error('VPN permission denied');
          }
        }
      }

      await OpenVPN.connect({
        address: 'your-server.com',
        username: 'your-username',
        password: 'your-password',
        openVPNConfig: 'your-config-string',
        
        iOSOptions: {
          localizedDescription: 'My VPN',
          networkExtensionBundleIdentifier: 'com.yourapp.vpn',
        },
        
        androidOptions: {
          Notification: {
            titleNotification: 'VPN Connected',
            titleConnected: 'Secure connection active',
            titleConnecting: 'Connecting to VPN...',
            showDisconnectAction: true,
          },
        },
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Connection failed';
      setError(errorMessage);
    }
  };

  // 🔌 Disconnect function
  const handleDisconnect = async () => {
    try {
      clearError();
      await OpenVPN.disconnect();
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Disconnect failed';
      setError(errorMessage);
    }
  };

  // 🎨 Get status info
  const getStatusInfo = () => {
    if (lastError) {
      return { emoji: '🔴', text: 'Error', color: '#F44336' };
    }
    
    switch (connectionState) {
      case OpenVPN.ConnectionState.CONNECTED:
        return { emoji: '🟢', text: 'Connected', color: '#4CAF50' };
      case OpenVPN.ConnectionState.CONNECTING:
        return { emoji: '🟡', text: 'Connecting...', color: '#FF9800' };
      case OpenVPN.ConnectionState.DISCONNECTING:
        return { emoji: '🟡', text: 'Disconnecting...', color: '#FF9800' };
      default:
        return { emoji: '⚫', text: 'Disconnected', color: '#9E9E9E' };
    }
  };

  const { emoji, text, color } = getStatusInfo();

  return (
    <View style={styles.container}>
      {/* Status Display */}
      <View style={[styles.statusCard, { borderColor: color }]}>
        <Text style={styles.statusEmoji}>{emoji}</Text>
        <Text style={[styles.statusText, { color }]}>{text}</Text>
        
        {serverAddress && (
          <Text style={styles.serverText}>Server: {serverAddress}</Text>
        )}
      </View>

      {/* Error Display */}
      {lastError && (
        <View style={styles.errorContainer}>
          <Text style={styles.errorText}> {lastError}</Text>
          <TouchableOpacity onPress={clearError} style={styles.clearButton}>
            <Text style={styles.clearButtonText}>Clear</Text>
          </TouchableOpacity>
        </View>
      )}

      {/* Action Buttons */}
      <View style={styles.buttonContainer}>
        {!isConnected && !isConnecting && (
          <TouchableOpacity style={styles.connectButton} onPress={handleConnect}>
            <Text style={styles.buttonText}>🔌 Connect VPN</Text>
          </TouchableOpacity>
        )}

        {isConnecting && (
          <TouchableOpacity style={styles.disabledButton} disabled>
            <Text style={styles.buttonText}>🔄 Connecting...</Text>
          </TouchableOpacity>
        )}

        {isConnected && (
          <TouchableOpacity style={styles.disconnectButton} onPress={handleDisconnect}>
            <Text style={styles.buttonText}>🔌 Disconnect VPN</Text>
          </TouchableOpacity>
        )}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  statusCard: {
    backgroundColor: 'white',
    padding: 24,
    borderRadius: 12,
    marginBottom: 20,
    borderWidth: 2,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  statusEmoji: {
    fontSize: 36,
    marginBottom: 8,
  },
  statusText: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  serverText: {
    fontSize: 14,
    color: '#666',
  },
  errorContainer: {
    backgroundColor: '#ffebee',
    padding: 12,
    borderRadius: 8,
    marginBottom: 20,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  errorText: {
    color: '#c62828',
    flex: 1,
  },
  clearButton: {
    paddingHorizontal: 12,
    paddingVertical: 4,
  },
  clearButtonText: {
    color: '#1976d2',
    fontWeight: 'bold',
  },
  buttonContainer: {
    gap: 12,
  },
  connectButton: {
    backgroundColor: '#4CAF50',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  disconnectButton: {
    backgroundColor: '#F44336',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  disabledButton: {
    backgroundColor: '#CCCCCC',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});

export default VPNConnectionPage;

🚨 Error Handling

Comprehensive error management and user-friendly error recovery patterns

🛡️ Comprehensive Error Handling

import React, { useState } from 'react';
import { Alert } from 'react-native';
import * as OpenVPN from 'react-native-openvpn';

const VPNWithErrorHandling = () => {
  const [errors, setErrors] = useState<string[]>([]);

  const handleVPNError = (error: any, context: string) => {
    let errorMessage = 'Unknown error occurred';
    
    if (error instanceof Error) {
      errorMessage = error.message;
    } else if (typeof error === 'string') {
      errorMessage = error;
    }
    
    const fullError = `${context}: ${errorMessage}`;
    setErrors(prev => [...prev, fullError]);
    
    console.error(fullError, error);
    
    // Show user-friendly error messages
    switch (context) {
      case 'CONNECTION':
        Alert.alert(
          'Connection Failed',
          'Unable to connect to VPN server. Please check your credentials and try again.',
          [{ text: 'OK' }]
        );
        break;
        
      case 'PERMISSION':
        Alert.alert(
          'Permission Required',
          'VPN permission is required to establish connection.',
          [
            { text: 'Cancel', style: 'cancel' },
            { text: 'Grant Permission', onPress: () => OpenVPN.prepare() },
          ]
        );
        break;
        
      case 'NETWORK':
        Alert.alert(
          'Network Error',
          'Please check your internet connection and try again.',
          [{ text: 'OK' }]
        );
        break;
        
      default:
        Alert.alert('Error', errorMessage, [{ text: 'OK' }]);
    }
  };

  const safeConnect = async (config: OpenVPN.ConnectionParams) => {
    try {
      // Android permission check with error handling
      if (Platform.OS === 'android') {
        try {
          const isPrepared = await OpenVPN.isPrepared();
          if (!isPrepared) {
            const granted = await OpenVPN.prepare();
            if (!granted) {
              throw new Error('User denied VPN permission');
            }
          }
        } catch (permError) {
          handleVPNError(permError, 'PERMISSION');
          return;
        }
      }
      
      // Validate configuration
      if (!config.address || !config.username || !config.password) {
        throw new Error('Missing required connection parameters');
      }
      
      if (!config.openVPNConfig && !config.openVPNConfigLocalFile) {
        throw new Error('OpenVPN configuration is required');
      }
      
      // Attempt connection with timeout
      const connectionPromise = OpenVPN.connect(config);
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Connection timeout')), 30000);
      });
      
      await Promise.race([connectionPromise, timeoutPromise]);
      
    } catch (connectionError) {
      // Categorize errors
      const errorMessage = connectionError.message.toLowerCase();
      
      if (errorMessage.includes('network') || errorMessage.includes('timeout')) {
        handleVPNError(connectionError, 'NETWORK');
      } else if (errorMessage.includes('auth') || errorMessage.includes('credential')) {
        handleVPNError(connectionError, 'AUTHENTICATION');
      } else if (errorMessage.includes('config') || errorMessage.includes('invalid')) {
        handleVPNError(connectionError, 'CONFIGURATION');
      } else {
        handleVPNError(connectionError, 'CONNECTION');
      }
    }
  };

  const safeDisconnect = async () => {
    try {
      await OpenVPN.disconnect();
    } catch (disconnectError) {
      handleVPNError(disconnectError, 'DISCONNECTION');
    }
  };

  // Clear errors
  const clearErrors = () => {
    setErrors([]);
  };

  return (
    <View>
      {/* Error display */}
      {errors.length > 0 && (
        <View style={styles.errorContainer}>
          <Text style={styles.errorTitle}>Recent Errors:</Text>
          {errors.slice(-3).map((error, index) => (
            <Text key={index} style={styles.errorText}>
               {error}
            </Text>
          ))}
          <TouchableOpacity onPress={clearErrors}>
            <Text style={styles.clearButton}>Clear Errors</Text>
          </TouchableOpacity>
        </View>
      )}
      
      {/* Your VPN UI */}
    </View>
  );
};

const styles = StyleSheet.create({
  errorContainer: {
    backgroundColor: '#ffebee',
    padding: 10,
    margin: 10,
    borderRadius: 5,
    borderLeftWidth: 4,
    borderLeftColor: '#f44336',
  },
  errorTitle: {
    fontWeight: 'bold',
    color: '#c62828',
    marginBottom: 5,
  },
  errorText: {
    color: '#d32f2f',
    fontSize: 12,
    marginBottom: 2,
  },
  clearButton: {
    color: '#1976d2',
    textAlign: 'right',
    marginTop: 5,
    textDecorationLine: 'underline',
  },
});

🎓 Summary

Essential VPN integration patterns for React Native applications

This guide provides practical examples for integrating React Native OpenVPN into your applications. Each example includes:

🔒 SecurityBest practices and secure configuration patterns
🎯 State ManagementSimple and efficient state management with Zustand
🚨 Error HandlingComprehensive error management with user-friendly recovery
📱 Cross-PlatformiOS and Android specific configurations and optimizations
🎨 UI/UXClean, responsive interfaces with real-time status indicators

🚀 Next Steps:


Built with ❤️ for secure, reliable VPN integration in React Native applications