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-permissionsyarn add react-native-webview react-native-permissionspnpm add react-native-webview react-native-permissionsiOS 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:
- Requests native microphone permissions using the
react-native-permissionspackage - Delegates permissions to the WebView when the embed agent requests microphone access
- Handles permission states across both native and WebView contexts
- 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 microphoneMODIFY_AUDIO_SETTINGS: Allows modification of audio settingsINTERNET: Required for WebView to load web contentACCESS_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
- App Launch: Android system checks for declared permissions
- Runtime Request: React Native requests microphone permission from user
- WebView Permission: WebView automatically grants microphone access to web content
- 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
- Launch the app and navigate to the WebView screen
- Check console logs for permission status
- Verify microphone access in web content
- 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:
- Deploy the embed agent on your target website
- Update the WebView source URL to point to your website
- 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
Permission Denied
Check the following requirements:
- ✅ Android manifest permissions declared
- ✅ Runtime permission requests implemented
- ✅ WebView permission handling configured
- ✅ 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);
};WebView Not Loading
Verify connectivity and configuration:
- ✅ Check internet connectivity
- ✅ Verify URL accessibility in browser
- ✅ Check WebView configuration settings
- ✅ 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);
}}
/>Microphone Not Working in WebView
Debug microphone access issues:
- ✅ Verify device microphone functionality
- ✅ Check app permissions in device settings
- ✅ Ensure WebView JavaScript is enabled
- ✅ 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
- Check the console logs for permission status
- Verify native permissions are granted
- Test WebView microphone access using test HTML files
- 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:
- Email Support: contact@revrag.ai
Last Updated: June 2025