Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 866
Unapedra Avatar asked Dec 14 '17 10:12

Unapedra


3 Answers

I think onPermissionRequest() method is duplicated in the latest version of react native webview package. I just commented the second one resolved my issue.

You should comment on the RNCWebViewManager.java file. You can get it from the following path node_modules\react-native-webview\android\src\main\java\com\reactnativecommunity\webview

enter image description here

enter image description here

like image 200
Codemaker Avatar answered Nov 15 '22 18:11

Codemaker


In my case the following code works.

Step 1: Add permissions to AndroidManifest.xml

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MICROPHONE" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- any additional permissions -->

Step 2: Add import statement for 'PermissionsAndroid' from 'react-native' package

import {
  PermissionsAndroid
} from 'react-native';

Step 3: Write async methods to acquire required permission, here I am getting Camera and Microphone permissions

cameraPermission = async () => {

    let granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.CAMERA,
      {
        title: "Camera Permission",
        message:
          "App needs access to your camera " +
          "so others can see you.",
        buttonNeutral: "Ask Me Later",
        buttonNegative: "Cancel",
        buttonPositive: "OK"
      }
    );
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      console.log("You can use the camera");
    } else {
      console.log("Camera permission denied");
    }
}

micPermission = async () => {

let granted = await PermissionsAndroid.request(
  PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
  {
    title: "Audio Permission",
    message:
      "App needs access to your audio / microphone",
    buttonNeutral: "Ask Me Later",
    buttonNegative: "Cancel",
    buttonPositive: "OK"
  }
);

if (granted === PermissionsAndroid.RESULTS.GRANTED) {
  console.log("You can use the Microphone");
} else {
  console.log("Microphone permission denied");
}  

micPermission = async () => {
    let granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
      {
        title: "Audio Permission",
        message:
          "App needs access to your audio / microphone",
        buttonNeutral: "Ask Me Later",
        buttonNegative: "Cancel",
        buttonPositive: "OK"
      }
    );

    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      console.log("You can use the Microphone");
    } else {
      console.log("Microphone permission denied");
    }    
}

Step 4: Trigger the permission on componentDidMount

componentDidMount() {
  this.cameraPermission();
  this.micPermission();
}

Step 5: Add WebView with media attributes

constructor(props) {
    super(props);
    this.state = {
      url: 'https://yoururl.com',
      userAgent: 'Mozilla/5.0 (Linux; An33qdroid 10; Android SDK built for x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.185 Mobile Safari/537.36'
    }
  }
render() {
return (
  <WebView
    userAgent={this.state.userAgent} //Set your useragent (Browser) **Very Important
    originWhitelist={['*']}
    allowsInlineMediaPlayback
    bounces={true}
    source={{
      uri: this.state.url, //URL of your redirect site
    }}
    startInLoadingState
    scalesPageToFit
    javaScriptEnabled={true}
  />
);
}

Hope this is helpful!

like image 40
Adheep Mohamed Abdul Kader Avatar answered Nov 15 '22 16:11

Adheep Mohamed Abdul Kader


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!

like image 37
Unapedra Avatar answered Nov 15 '22 18:11

Unapedra