Skip to main content

React Native WebView Integration Guide

Overview

This React Native WebView integration provides a solution for the microphone access challenge faced by Revrag’s embed agent when implemented in mobile apps using WebView. The embed agent requires microphone access to enable voice-based AI assistance for website navigation, but traditional WebView implementations often fail to properly delegate microphone permissions to the embedded website.

Problem Statement

Revrag’s embed agent is a JavaScript SDK that can be integrated into any website to provide AI-powered voice assistance. However, when mobile apps load websites containing the embed agent in a WebView, the microphone access required by the agent is often blocked or not properly delegated, preventing users from interacting with the AI assistant.

Prerequisites

  • React Native 0.72+
  • iOS 13+ / Android API 21+
  • Microphone permissions on target device
This integration requires proper setup of WebView microphone permissions and real-time audio communication capabilities.

Installation

Install the required packages using your preferred package manager:
npm install react-native-webview react-native-permissions

iOS Pod Installation

For iOS, run pod install after installing dependencies:
cd ios && pod install && cd ..

Solution Overview

This project implements a React Native WebView solution using the react-native-webview package with proper microphone permission handling. The solution:
  1. Requests native microphone permissions using the react-native-permissions package
  2. Delegates permissions to the WebView when the embed agent requests microphone access
  3. Handles permission states across both native and WebView contexts
  4. Provides a seamless user experience for voice interactions

Key Features

  • Native Microphone Permission Handling: Properly requests and manages microphone permissions at the app level
  • WebView Permission Delegation: Seamlessly delegates permissions to the embedded website
  • Cross-Platform Support: Works on both Android and iOS
  • Error Handling: Comprehensive error handling and user feedback
  • Configurable: Easy configuration for different websites and use cases

Android Configuration

1. Android Manifest Permissions

Add the following permissions to your android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Required permissions for WebView microphone access -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:allowBackup="false"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true">

        <!-- Your activities and other components -->

    </application>
</manifest>
Permission Purposes:
  • RECORD_AUDIO: Grants permission to record audio from microphone
  • MODIFY_AUDIO_SETTINGS: Allows modification of audio settings
  • INTERNET: Required for WebView to load web content
  • ACCESS_NETWORK_STATE: Required for network connectivity checks

2. Android MainActivity Configuration

Update your android/app/src/main/java/com/yourapp/MainActivity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    val webView = WebView(this)
    webView.settings.javaScriptEnabled = true
    webView.settings.mediaPlaybackRequiresUserGesture = false
    
    webView.webChromeClient = object : WebChromeClient() {
        override fun onPermissionRequest(request: PermissionRequest?) {
            request?.grant(request.resources) // Grant mic/camera
        }
    }
}
Key Configuration Features:
  • JavaScript Enabled: Allows web content to execute JavaScript code
  • Media Playback: Disables user gesture requirement for media playback
  • Permission Handler: Automatically grants microphone and camera permissions

iOS Configuration

1. iOS Permissions

CRITICAL: Add the following permissions to your ios/YourAppName/Info.plist. Missing NSMicrophoneUsageDescription will cause the app to crash when accessing the microphone.
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone to enable voice functionality in the webview.</string>

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

WebView Implementation

Permission Request Handling

import React, { useState, useEffect } from 'react';
import { PermissionsAndroid, Platform } from 'react-native';
import { WebView } from 'react-native-webview';

const MicrophoneWebView = () => {
  const [micGranted, setMicGranted] = useState(false);

  useEffect(() => {
    async function requestMic() {
      if (Platform.OS === 'android') {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
          {
            title: 'Microphone Permission',
            message: 'App needs access to your microphone',
            buttonPositive: 'OK',
          },
        );
        setMicGranted(granted === PermissionsAndroid.RESULTS.GRANTED);
      } else {
        setMicGranted(true); // iOS will prompt automatically via Info.plist
      }
    }
    requestMic();
  }, []);

  return (
    // WebView component here
  );
};

WebView Configuration

<WebView
  source={{ uri: 'https://your-website-with-revrag-agent.com' }}
  javaScriptEnabled={true}
  domStorageEnabled={true}
  allowsInlineMediaPlayback={true}
  mediaPlaybackRequiresUserAction={false}
  injectedJavaScript={injectedJS}
  originWhitelist={['*']}
  allowFileAccess={true}
  allowUniversalAccessFromFileURLs={true}
  startInLoadingState={true}
  onPermissionRequest={(event) => {
    console.log('Permission request from WebView:', event.resources);
    // Grant only microphone
    if (event.resources.includes('android.webkit.resource.AUDIO_CAPTURE')) {
      event.grant(['android.webkit.resource.AUDIO_CAPTURE']);
    } else {
      event.deny();
    }
  }}
  onError={(syntheticEvent) => {
    const { nativeEvent } = syntheticEvent;
    console.error('WebView error: ', nativeEvent);
  }}
/>

JavaScript Injection for Testing

const injectedJS = `
  (function() {
    console.log("Injected JS: Testing microphone access...");
    
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
      navigator.mediaDevices.getUserMedia({ audio: true })
        .then(stream => {
          console.log("Microphone access granted in WebView");
          // Stop the stream immediately after testing
          stream.getTracks().forEach(track => track.stop());
        })
        .catch(err => {
          console.error("Microphone access denied in WebView:", err);
        });
    } else {
      console.error("getUserMedia not supported in this WebView");
    }
    
    return true; // Required for Android
  })();
`;

Platform-Specific Configuration

Android

The Android configuration includes native WebView settings and permission handling as shown above.

iOS

Add the microphone usage description to ios/YourApp/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone to enable voice functionality in the webview.</string>

Permission Flow

  1. App Launch: Android system checks for declared permissions
  2. Runtime Request: React Native requests microphone permission from user
  3. WebView Permission: WebView automatically grants microphone access to web content
  4. Web Content Access: JavaScript can access microphone via navigator.mediaDevices.getUserMedia()

Testing

1. Test HTML Files

Create test files in android/app/src/main/assets/ for local testing: test-microphone.html:
<!DOCTYPE html>
<html>
<head>
    <title>Microphone Test</title>
</head>
<body>
    <h1>Microphone Access Test</h1>
    <button onclick="testMicrophone()">Test Microphone</button>
    <div id="status"></div>
    
    <script>
        function testMicrophone() {
            navigator.mediaDevices.getUserMedia({ audio: true })
                .then(stream => {
                    document.getElementById('status').innerHTML = 'Microphone access granted!';
                    stream.getTracks().forEach(track => track.stop());
                })
                .catch(err => {
                    document.getElementById('status').innerHTML = 'Microphone access denied: ' + err;
                });
        }
    </script>
</body>
</html>

2. Manual Testing Steps

  1. Launch the app and navigate to the WebView screen
  2. Check console logs for permission status
  3. Verify microphone access in web content
  4. Test audio recording functionality

3. Debug Information

  • Console logs show permission request status
  • WebView error handling for failed loads
  • JavaScript console messages are captured

Integration with Revrag Embed Agent

To integrate this solution with Revrag’s embed agent:
  1. Deploy the embed agent on your target website
  2. Update the WebView source URL to point to your website
  3. Test microphone access by triggering the embed agent’s voice functionality
The embed agent should now be able to access the microphone and provide voice-based assistance to users.

Troubleshooting

Common Issues

Check the following requirements:
  1. ✅ Android manifest permissions declared
  2. ✅ Runtime permission requests implemented
  3. ✅ WebView permission handling configured
  4. ✅ App permissions granted in device settings
// Check permission status
const checkPermission = async () => {
  const granted = await PermissionsAndroid.check(
    PermissionsAndroid.PERMISSIONS.RECORD_AUDIO
  );
  console.log('Microphone permission:', granted);
};
Verify connectivity and configuration:
  1. ✅ Check internet connectivity
  2. ✅ Verify URL accessibility in browser
  3. ✅ Check WebView configuration settings
  4. ✅ Ensure JavaScript is enabled
// Add error handling
<WebView
  onError={(syntheticEvent) => {
    const { nativeEvent } = syntheticEvent;
    console.error('WebView error:', nativeEvent);
  }}
  onHttpError={(syntheticEvent) => {
    const { nativeEvent } = syntheticEvent;
    console.error('HTTP error:', nativeEvent.statusCode);
  }}
/>
Debug microphone access issues:
  1. ✅ Verify device microphone functionality
  2. ✅ Check app permissions in device settings
  3. ✅ Ensure WebView JavaScript is enabled
  4. ✅ Test with microphone test HTML
// Test microphone access
const testMicrophone = `
  navigator.mediaDevices.getUserMedia({ audio: true })
    .then(stream => {
      console.log("Microphone access granted");
      stream.getTracks().forEach(track => track.stop());
    })
    .catch(err => console.error("Microphone error:", err));
`;

Debug Steps

  1. Check the console logs for permission status
  2. Verify native permissions are granted
  3. Test WebView microphone access using test HTML files
  4. Ensure the target website supports microphone access

Best Practices

Security & Permissions:
  • Only grant necessary permissions to WebView
  • Restrict WebView to trusted domains using origin whitelist
  • Always request user permission before accessing microphone
  • Ensure audio data is handled securely and privately
Performance Optimization:
  • Load WebView only when needed (lazy loading)
  • Cache permission status to avoid repeated requests
  • Implement graceful fallbacks for permission denials
  • Properly dispose of WebView resources to prevent memory leaks
Configuration Management:
  • Use environment variables for sensitive information
  • Create reusable configuration objects
  • Test with different target URLs during development
  • Monitor WebView performance and error rates

Usage Examples

Complete Implementation Example

import React, { useState, useEffect } from 'react';
import { 
  View, 
  StyleSheet, 
  PermissionsAndroid, 
  Platform, 
  Alert 
} from 'react-native';
import { WebView } from 'react-native-webview';

const MicrophoneWebView = ({ targetUrl = 'https://your-website.com' }) => {
  const [micGranted, setMicGranted] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    requestMicrophonePermission();
  }, []);

  const requestMicrophonePermission = async () => {
    try {
      if (Platform.OS === 'android') {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
          {
            title: 'Microphone Permission',
            message: 'This app needs access to your microphone for voice features',
            buttonPositive: 'Allow',
            buttonNegative: 'Deny',
          },
        );
        setMicGranted(granted === PermissionsAndroid.RESULTS.GRANTED);
      } else {
        // iOS will prompt automatically via Info.plist
        setMicGranted(true);
      }
    } catch (error) {
      console.error('Permission request failed:', error);
      Alert.alert('Error', 'Failed to request microphone permission');
    }
    setIsLoading(false);
  };

  const handlePermissionRequest = (event) => {
    const { resources } = event;
    if (resources.includes('android.webkit.resource.AUDIO_CAPTURE')) {
      event.grant(['android.webkit.resource.AUDIO_CAPTURE']);
    } else {
      event.deny();
    }
  };

  if (isLoading) {
    return (
      <View style={styles.loadingContainer}>
        <Text>Requesting permissions...</Text>
      </View>
    );
  }

  if (!micGranted) {
    return (
      <View style={styles.errorContainer}>
        <Text>Microphone permission is required for voice features</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <WebView
        source={{ uri: targetUrl }}
        javaScriptEnabled={true}
        domStorageEnabled={true}
        allowsInlineMediaPlayback={true}
        mediaPlaybackRequiresUserAction={false}
        onPermissionRequest={handlePermissionRequest}
        onError={(syntheticEvent) => {
          const { nativeEvent } = syntheticEvent;
          console.error('WebView error:', nativeEvent);
        }}
        onLoadStart={() => setIsLoading(true)}
        onLoadEnd={() => setIsLoading(false)}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  errorContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
});

export default MicrophoneWebView;

Support

For additional help:
Last Updated: June 2025