# React Native WebView

> React Native WebView Integration Guide - Enable microphone access for Revrag Embed Agent in mobile apps

URL: /embed/web-view-integration/react-native
Markdown: /embed/web-view-integration/react-native.md



{/* 
  <img
  className="block dark:hidden"
  src="https://images.unsplash.com/photo-1555774698-0b77e0d5fac6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1400&q=72"
  alt="React Native WebView Development Light"
  />
  <img
  className="hidden dark:block"
  src="https://images.unsplash.com/photo-1517077304055-6e89abbf09b0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1400&q=72"
  alt="React Native WebView Development Dark"
  /> */}

# React Native WebView Integration Guide [#react-native-webview-integration-guide]

## Overview [#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 [#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 [#prerequisites]

* React Native 0.72+
* iOS 13+ / Android API 21+
* Microphone permissions on target device

<Note>
  This integration requires proper setup of WebView microphone permissions and real-time audio communication capabilities.
</Note>

## Installation [#installation]

Install the required packages using your preferred package manager:

<CodeGroup>
  ```bash npm
  npm install react-native-webview react-native-permissions
  ```

  ```bash yarn
  yarn add react-native-webview react-native-permissions
  ```

  ```bash pnpm
  pnpm add react-native-webview react-native-permissions
  ```
</CodeGroup>

### iOS Pod Installation [#ios-pod-installation]

For iOS, run pod install after installing dependencies:

```bash
cd ios && pod install && cd ..
```

## Solution Overview [#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 [#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 [#android-configuration]

### 1. Android Manifest Permissions [#1-android-manifest-permissions]

Add the following permissions to your `android/app/src/main/AndroidManifest.xml`:

```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>
```

<Note>
  **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
</Note>

### 2. Android MainActivity Configuration [#2-android-mainactivity-configuration]

Update your `android/app/src/main/java/com/yourapp/MainActivity.kt`:

```kotlin
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
        }
    }
}
```

<Note>
  **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
</Note>

## iOS Configuration [#ios-configuration]

### 1. iOS Permissions [#1-ios-permissions]

<Warning>
  **CRITICAL:** Add the following permissions to your `ios/YourAppName/Info.plist`. Missing `NSMicrophoneUsageDescription` will cause the app to crash when accessing the microphone.
</Warning>

```xml
<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 [#webview-implementation]

### Permission Request Handling [#permission-request-handling]

```jsx
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-configuration]

```jsx
<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 [#javascript-injection-for-testing]

```javascript
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 [#platform-specific-configuration]

### Android [#android]

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

### iOS [#ios]

Add the microphone usage description to `ios/YourApp/Info.plist`:

```xml
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone to enable voice functionality in the webview.</string>
```

## Permission Flow [#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 [#testing]

### 1. Test HTML Files [#1-test-html-files]

Create test files in `android/app/src/main/assets/` for local testing:

**test-microphone.html**:

```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 [#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 [#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 [#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 [#troubleshooting]

### Common Issues [#common-issues]

<AccordionGroup>
  <Accordion title="Permission Denied">
    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

    ```jsx
    // Check permission status
    const checkPermission = async () => {
      const granted = await PermissionsAndroid.check(
        PermissionsAndroid.PERMISSIONS.RECORD_AUDIO
      );
      console.log('Microphone permission:', granted);
    };
    ```
  </Accordion>

  <Accordion title="WebView Not Loading">
    Verify connectivity and configuration:

    1. ✅ Check internet connectivity
    2. ✅ Verify URL accessibility in browser
    3. ✅ Check WebView configuration settings
    4. ✅ Ensure JavaScript is enabled

    ```jsx
    // 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);
      }}
    />
    ```
  </Accordion>

  <Accordion title="Microphone Not Working in WebView">
    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

    ```jsx
    // 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));
    `;
    ```
  </Accordion>
</AccordionGroup>

### Debug Steps [#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 [#best-practices]

<Tip>
  **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
</Tip>

<Tip>
  **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
</Tip>

<Tip>
  **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
</Tip>

## Usage Examples [#usage-examples]

### Complete Implementation Example [#complete-implementation-example]

```jsx
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 [#support]

For additional help:

* **Email Support:** [contact@revrag.ai](mailto:contact@revrag.ai)

***

**Last Updated:** June 2025
