Search code examples
androidreact-nativewebviewandroid-webviewgetusermedia

React Native: Webview getUserMedia not working (onPermissionRequest override?)


I'm developing an application for iOS, Android and Windows with React Native in which I need to show a webpage through a WebView. That webpage accesses the camera of the device, so it uses MediaDevices.getUserMedia() Javascript function.

It works without problems in desktop, even in the Chrome app on the smartphone. However, when I call the webpage via the React Native <WebView />, I get a PermissionDenied error. The problem is that no request is shown to let me accept that permission. It justs denies the request without asking.

Here is a sample of my WebView element:

    <WebView 
        style={{flex: 1}}
        mediaPlaybackRequiresUserAction={false}
        domStorageEnabled={true}
        allowsInlineMediaPlayback={true}
        source={{uri: 'https://myurltomyapp.com/index.html'}} 
        startInLoadingState={true}
        allowUniversalAccessFromFileURLs={true}
    />

I've got all necessary permissions set on AndroidManifest.xml (and even some not needed for the sake of testing):

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
    <uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" />
    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.RECORD_VIDEO"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

And yes, the URL where is hosted the website is SSL certified, so it goes through HTTPS. It works on Chrome app, so it should work on WebView, too.

I've been reading through the net, and it seems that's because React Native WebView lacks of a onPermissionRequest() implementation, so it fails silently (and denies it).

Is there a way to implement it overriding the native WebView in React Native? Which files do I have to edit, if possible? Is there any third-party module (up to date) which has this implemented (haven't been able to find any)?

Thank you.


Solution

  • Finally I had to implement an own WebView component in native Android. The problem is the onPermissionRequest() function that it's not implemented in the native WebView. You have to create your own and override that function. In my case, I had to add the following code (all files are under path [react-native-project]/android/app/src/main/java/com/listproject/permissionwebview/):

    In PermissionWebviewPackage.java:

    package com.listproject.permissionwebview;
    
    import com.facebook.react.ReactPackage;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.uimanager.ViewManager;
    
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Add this package to getPackages() method in MainActivity.
     */
    
    public class PermissionWebviewPackage implements ReactPackage {
    
        @Override
        public List<ViewManager> createViewManagers(
                ReactApplicationContext reactContext) {
            return Arrays.<ViewManager>asList(
                new PermissionWebviewViewManager()
              );
        }
    
        @Override
        public List<NativeModule> createNativeModules(
                ReactApplicationContext reactContext) {
            return Collections.emptyList();
        }
    }
    

    In PermissionWebviewView.java:

    package com.listproject.permissionwebview;
    import android.content.Context;
    import android.widget.LinearLayout;
    import android.os.Bundle;
    import android.widget.Toast;
    import android.webkit.WebChromeClient;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    import android.webkit.PermissionRequest;
    import android.webkit.WebSettings;
    import android.net.http.SslCertificate;
    import android.net.http.SslError;
    import android.webkit.SslErrorHandler;
    import android.support.v4.app.ActivityCompat;
    import android.app.LocalActivityManager;
    import android.view.ViewGroup;
    import android.Manifest;
    import android.app.Activity;
    import com.listproject.MainActivity;
    import com.listproject.R;
    
    public class PermissionWebviewView extends WebView{
    
        private Context context;
    
        public PermissionWebviewView(Context context) {
            super(context);
            this.context = context;
    
            this.setWebViewClient(new WebViewClient());
    
            WebSettings webSettings = this.getSettings();
            webSettings.setJavaScriptEnabled(true);
            webSettings.setAllowFileAccessFromFileURLs(true);
            webSettings.setAllowUniversalAccessFromFileURLs(true);
            webSettings.setMediaPlaybackRequiresUserGesture(false);
            webSettings.setUseWideViewPort(true);
            webSettings.setDomStorageEnabled(true);
    
            this.setWebChromeClient(new WebChromeClient() {
                @Override
                public void onPermissionRequest(final PermissionRequest request) {
                    request.grant(request.getResources());
                }
            });
        }
    }
    

    In PermissionWebviewViewManager.java:

    package com.listproject.permissionwebview;
    
    import com.facebook.react.uimanager.SimpleViewManager;
    import com.facebook.react.uimanager.ThemedReactContext;
    
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContext;
    import com.facebook.react.uimanager.annotations.ReactProp;
    
    public class PermissionWebviewViewManager extends SimpleViewManager<PermissionWebviewView> {
    
        public static final String REACT_CLASS = "PermissionWebviewViewManager";
        private String source;
    
        @Override
        public String getName() {
            return REACT_CLASS;
        }
    
        @Override
        public PermissionWebviewView createViewInstance(ThemedReactContext context) {
            return new PermissionWebviewView(context); //If your customview has more constructor parameters pass it from here.
        }
    
        @ReactProp(name = "sourceUri")
        public void setSource(PermissionWebviewView view, String source) {
            view.loadUrl(source);
        }
    }
    

    Finally, update your MainApplication.java file, and add your package into getPackages() function:

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
            [...],
            new PermissionWebviewPackage()
      );
    }
    

    Keep in mind that the names of listproject, permissionwebview, and so on can be changed to whatever you need to, as long as you change them in the namespaces of the files and in the package references.

    Once you have all this, all you have to create is the ReactNative component export ([react-native-project]/app/components/PermissionWebView/index.android.js):

    import PropTypes from 'prop-types';
    import {requireNativeComponent, ViewPropTypes} from 'react-native';
    
    // The 'name' property is not important, the important one is below
    var mcs = {
      name: 'PermissionWebview',
      propTypes: {
        sourceUri: PropTypes.string,
          ...ViewPropTypes
      }
    };
    
    module.exports = requireNativeComponent('PermissionWebviewViewManager', mcs);
    

    Note we set the name of index to index.android.js because this component is made for Android, so it's included only in Android platform.

    With this, everything should work and the WebView component should ask for permission when it's used.

    EDIT:

    I've uploaded the code to my Github repository. I've only changed some names, but the core code has not changed a bit to make sure it still works (I cannot test it, so better not to change lots of things).

    Hope it helps someone!