3DS2 BrowserInfo data with React Native and Expo

410 Views Asked by At

In order to integrate the 3DS2 protocol of my payment provider (MangoPay), I have to give the BrowserInfo data.

Here is an example:

{
"BrowserInfo": {
        "AcceptHeader" : "application/json,text/javascript,*/*;q=0.01<", 
        "JavaEnabled": true,
        "Language":"fr",
        "ColorDepth": 32,
        "ScreenHeight": 667,
        "ScreenWidth": 375, 
        "TimeZoneOffset": "-120" 
        "UserAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" 
        "JavascriptEnabled": true
}

Currently, I am able to open a browser instance using WebBrowser.openBrowserAsync.

How can I retrieve those information using React Native + Expo?

2

There are 2 best solutions below

3
Soullivaneuh On BEST ANSWER

The only doable way I found is a multiple steps process.

Public web page

Create and host a public web page doing the following:

  1. Fetch the browser info onto an object
  2. Redirect to the given redirect URL with the info

In a nutshell:

<!DOCTYPE html>
<html lang="en">
  <body>
    <script type="text/javascript">
      const browserInfo = {
        // Currently unable to fetch the default value automatically.
        // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
        acceptHeader: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
        javaEnabled: window.navigator.javaEnabled(),
        language: window.navigator.language,
        colorDepth: window.screen.colorDepth,
        screenHeight: window.screen.availHeight,
        screenWidth: window.screen.availWidth,
        timeZoneOffset: window.navigator.timeZoneOffset,
        userAgent: window.navigator.userAgent,
        javascriptEnabled: true,
      }
      console.log('browserInfo', browserInfo);

      const urlParams = new URLSearchParams(window.location.search);

      if (urlParams.has('redirectUrl')) {
        const redirectUrl = new URL(urlParams.get('redirectUrl'));
        Object.entries(browserInfo).forEach(([key, value]) => {
          redirectUrl.searchParams.append(key, value);
        });
        document.location = redirectUrl.toString();
      }
    </script>
  </body>
</html>

Expo app redirect fetching

On expo, you have two things to do:

  1. Start an event listener to fetch the redirect
  2. Open a WebBrowser instance to the previous public page

Example:

import React, { FC, useCallback } from 'react';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
import * as queryString from 'query-string';
import {
  Button,
} from '@kidways/apps-components';
import {
  ButtonProps,
  View,
} from 'react-native';
import { useFocusEffect } from '@react-navigation/native';

const App: FC = () => {
  const urlEventHandler = (event) => {
    const parsedUrl = queryString.parseUrl(event.url);
    const path = parsedUrl.url.split('/--/')[1];

    if (
      path === 'browser-info'
    ) {
      // At this point, you have all the needed information.
      console.log('browser-info', parsedUrl.query);
    }
  };

  useFocusEffect(
    useCallback(() => {
      Linking.addEventListener('url', urlEventHandler);

      return () => Linking.removeEventListener('url', urlEventHandler);
    }, []),
  );

  const handleBrowserInfoTest: ButtonProps['onPress'] = () => {
    WebBrowser.openBrowserAsync(
      'https://your.domain/browser.html?redirectUrl='
      + Linking.createURL('browser-info')
    );
  }

  return (
    <View>
      <Button
        title="Browser Info"
        onPress={handleBrowserInfoTest}
      />
    </View>
  );
};

With this code, you have a way to fetch the browser info just by pressing the button.

This solution is not ideal because of the user browser opening to achieve that, but I am afraid there is currently no other way.

0
salzo On

What about using a WebView with some JavaScript code that posts back a message with the required browser info?

You could have the WebView HTML+JavaScript document hardcoded in your React Native JavaScript file like this (untested code, treat it like pseudo-code):

const mobileWebView3DsBrowserDataHtml = `
<!DOCTYPE html>
<html>
<body onload="post3DSBrowserInfo()">
<script>
function post3DSBrowserInfo() {
  var threeDsBrowserData = {
    java_enabled:
      typeof window.navigator.javaEnabled === 'function'
        ? window.navigator.javaEnabled()
        : false,
    language: window.navigator.language,
    color_depth: window.screen.colorDepth,
    screen_height: window.screen.height,
    screen_width: window.screen.width,
    timezone: new Date().getTimezoneOffset(),
  };
  window.ReactNativeWebView.postMessage(
    JSON.stringify({
      type: "3ds-browser-data",
      payload: threeDsBrowserData,
    })
  );
}
</script>
</body>
</html>
`

And then use it in the WebView:

<WebView
  source={{
    html: mobileWebView3DsBrowserDataHtml,
  }}
  onMessage={event => {
    const browserDataMsg = JSON.parse(event?.nativeEvent?.data || {});
    if (browserDataMsg.type === '3ds-browser-data') {
      // do what you need with the browser info
      setBrowserData(browserDataMsg.payload);
    }
  }}
/>

Or you could even bring the JavaScript browser info obtaining code from the server:

const mobileWebView3DsBrowserDataHtml =
  '<!DOCTYPE html><html><body></body></html>';


const MyComponent = () => {

  const [mobileWebView3DsBrowserDataJS, setMobileWebView3DsBrowserDataJS] = useState('');
  const [browserData, setBrowserData] = useState({});

  useFocusEffect(
    React.useCallback(() => {
      async function fetch3DsBrowserDataJS() {
        const js = await apiService.getBrowserDataJs();
        setMobileWebView3DsBrowserDataJS(js);
      }

      fetch3DsBrowserDataJS();
    }, []),
  );

  //...

Where the data obtained from the server would be something like

(function () {
  var threeDsBrowserData = {
    java_enabled:
      typeof window.navigator.javaEnabled === 'function'
        ? window.navigator.javaEnabled()
        : false,
    language: window.navigator.language,
    color_depth: window.screen.colorDepth,
    screen_height: window.screen.height,
    screen_width: window.screen.width,
    timezone: new Date().getTimezoneOffset(),
  };
  window.ReactNativeWebView.postMessage(
    JSON.stringify({
      type: "3ds-browser-data",
      payload: threeDsBrowserData,
    })
  );
})()

And then in the WebView:

<WebView
  source={{
    html: mobileWebView3DsBrowserDataHtml,
  }}
  injectedJavaScript={mobileWebView3DsBrowserDataJS}
  onMessage={event => {
    const browserDataMsg = JSON.parse(event?.nativeEvent?.data || {});
    if (browserDataMsg.type === '3ds-browser-data') {
      setBrowserData(browserDataMsg.payload);
    }
  }}
/>