├── .gitignore ├── LICENSE ├── MessageWebView.js ├── README.md ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Akash Sant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MessageWebView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { WebView, View } from 'react-native' 3 | 4 | // fix https://github.com/facebook/react-native/issues/10865 5 | const patchPostMessageJsCode = `(${String(function() { 6 | var originalPostMessage = window.postMessage 7 | var patchedPostMessage = function(message, targetOrigin, transfer) { 8 | originalPostMessage(message, targetOrigin, transfer) 9 | } 10 | patchedPostMessage.toString = function() { 11 | return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage') 12 | } 13 | window.postMessage = patchedPostMessage 14 | })})();` 15 | 16 | export default class MessageWebView extends React.Component { 17 | constructor(props) { 18 | super(props) 19 | this.postMessage = this.postMessage.bind(this) 20 | } 21 | postMessage(action) { 22 | this.WebView.postMessage(JSON.stringify(action)) 23 | } 24 | 25 | getWebViewHandle = () => { 26 | return this.webview; 27 | } 28 | 29 | render() { 30 | const { html, source, url, onMessage, ...props } = this.props 31 | 32 | return ( 33 | 34 | {this.webview = x}} 42 | onMessage={e => onMessage(e.nativeEvent.data)} 43 | 44 | /> 45 | 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-recaptcha 2 | A react native wrapper for google recaptcha v3 3 | 4 | ## Installation 5 | ``` 6 | npm install --save react-native-recaptcha-v3 7 | ``` 8 | 9 | ## Usage 10 | ``` 11 | 12 | ``` 13 | 14 | ### Props 15 | 16 | * `containerStyle` An object that specifies the display style for the reCaptcha badge. 17 | 18 | * `siteKey` A string representing the siteKey provided in the Google reCaptcha admin console. 19 | 20 | * `url` URL associated with the app (This is the domain url that you registered on Google Admin Console when getting a siteKey) 21 | 22 | * `action` A string representing the ReCaptcha action (Refer to the ReCaptcha v3 document) 23 | 24 | * `reCaptchaType`: Currently two types of reCaptchas are supported: 25 | * `invisible`: Invisible reCaptcha do not require the users to solve a challenge. Refer to the reCaptcha V3 documentation for further information 26 | * `normal`: Normal reCaptcha may often require the user to click on a "I am not a robot" checkbox and solve a challenge (reCaptcha V2) - NOTE: This is meant to be used only with the firebase projects since firebase doesn't yet support reCaptcha v3. 27 | 28 | * `config`: Firebase project config found in the firebase console. This prop is only required when using the normal reCaptcha 29 | 30 | * `onExecute` A function to handle the response of ReCaptcha. Takes in a parameter that represents the 31 | response token from the ReCaptcha. 32 | 33 | ### Contribution 34 | 35 | Feel like contribution to this repository? The steps are simple: 36 | * Fork the repository 37 | * Make the changes you'd like to see 38 | * Create a PR and wait for it to be approved by two people before merging 39 | 40 | ### Applications using `react-native-recaptcha-v3` 41 | 42 | [Pool App](https://www.poolapp.io) : https://www.poolapp.io 43 | 44 | #### Thank-you for using `react-native-recaptcha-v3` 😀 Feel free to also leave any feedback or change requests you may have. 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import MessageWebView from './MessageWebView'; 3 | import PropTypes from 'prop-types'; 4 | import {Platform, Linking} from 'react-native'; 5 | 6 | const RECAPTCHA_SUB_STR="https://www.google.com/recaptcha/api2/anchor?"; 7 | const RECAPTCHA_SUB_STR_FRAME="https://www.google.com/recaptcha/api2/bframe"; 8 | 9 | export const type = Object.freeze({"invisible": 1, "normal": 2}); 10 | 11 | const getInvisibleRecaptchaContent = (siteKey, action, onReady) => { 12 | const webForm = ' ' + 13 | ' ' + 14 | ' ' + 15 | '' + 23 | ''; 24 | return webForm; 25 | } 26 | 27 | const getNormalRecaptchaContent = (config) => { 28 | const webForm = ' '+ 29 | ' '+ 30 | ' '+ 31 | ' '+ 32 | ' '+ 34 | ' '+ 35 | '
'+ 36 | ''+ 43 | ''+ 44 | ' '; 45 | return webForm; 46 | } 47 | 48 | export default class ReCaptcha extends Component { 49 | 50 | static propTypes = { 51 | onMessage: PropTypes.func, 52 | containerStyle: PropTypes.any, 53 | siteKey: PropTypes.string.isRequired, 54 | url: PropTypes.string.isRequired, 55 | action: PropTypes.string, 56 | onReady: PropTypes.func, 57 | onExecute: PropTypes.func, 58 | customWebRecaptcha: PropTypes.func, 59 | reCaptchaType: PropTypes.oneOf(Object.values(type)).isRequired, 60 | }; 61 | 62 | static defaultProps = { 63 | onReady: () => {}, 64 | onExecute: () => {}, 65 | action: '', 66 | containerStyle: { 67 | width: '100%', 68 | height: '100%', 69 | zIndex: -1, 70 | position: 'relative', 71 | marginBottom: 20 72 | }, 73 | reCaptchaType: type.invisible 74 | }; 75 | 76 | onShouldStartLoadWithRequest = (event) => { 77 | const {config, url} = this.props; 78 | if (event.url === url || event.url.indexOf(RECAPTCHA_SUB_STR) !== -1 || (!!config && event.url.indexOf(config.authDomain) !== -1) || event.url.indexOf(RECAPTCHA_SUB_STR_FRAME) !== -1) { 79 | return true; 80 | } 81 | Linking.canOpenURL(event.url).then(supported => { 82 | if (!supported) { 83 | console.log('Can\'t handle url: ' + url); 84 | } else { 85 | return Linking.openURL(event.url); 86 | } 87 | }); 88 | return false; 89 | } 90 | 91 | onNavigationStateChange = (event) => { 92 | if (Platform.OS === 'android') { 93 | const {url} = this.props; 94 | if (url !== event.url && event.url.indexOf(RECAPTCHA_SUB_STR) === -1 && !!event.canGoBack && !event.loading) { 95 | Linking.canOpenURL(event.url).then(supported => { 96 | if(!supported) { 97 | console.log('Can\'t handle url: ' + url); 98 | } else { 99 | return Linking.openUrl(event.url); 100 | } 101 | }); 102 | } 103 | 104 | if (!!event.canGoBack) { 105 | this.webview.getWebViewHandle().goBack(); 106 | } 107 | } 108 | 109 | } 110 | 111 | render() { 112 | const { 113 | containerStyle, 114 | siteKey, 115 | action, 116 | onReady, 117 | onExecute, 118 | config, 119 | reCaptchaType, 120 | url 121 | } = this.props; 122 | 123 | return ( 124 | { this.webview = ref ;}} 126 | scalesPageToFit={true} 127 | mixedContentMode={'always'} 128 | containerStyle={containerStyle} 129 | onMessage={(message) => onExecute(message)} 130 | source={{ 131 | html: reCaptchaType == type.invisible ? getInvisibleRecaptchaContent(siteKey, action, onReady) : 132 | getNormalRecaptchaContent(config), 133 | baseUrl: url 134 | }} 135 | onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest} 136 | onNavigationStateChange = {this.onNavigationStateChange} 137 | /> 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-recaptcha-v3", 3 | "version": "0.0.14", 4 | "description": "A react native wrapper for Google ReCaptcha V3", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/acsant/react-native-recaptcha.git" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "react-native", 16 | "native", 17 | "wrapper", 18 | "google", 19 | "recaptcha", 20 | "v3", 21 | "security" 22 | ], 23 | "author": "Akash Sant", 24 | "license": "ISC", 25 | "dependencies": { 26 | "prop-types": "^15.6.1", 27 | "merge": ">=1.2.1" 28 | }, 29 | "peerDependencies": { 30 | "react-native": "*" 31 | } 32 | } 33 | --------------------------------------------------------------------------------