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:
Requests native microphone permissions using the react-native-permissions package
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 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
})();
` ;
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
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
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 );
};
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:
Last Updated: June 2025