From 737b2839e2cd97068633078fd50281a34249dc39 Mon Sep 17 00:00:00 2001 From: Alexande B Date: Sun, 3 Nov 2024 21:51:47 +0100 Subject: [PATCH 1/5] feat: improve slow network experience - add `closableLoading` property to allow cancel hCaptcha during loading - `hasBackdrop=false` now affect `backgroundColor` and no backgroun will be shown till hCaptcha is loaded --- Hcaptcha.d.ts | 4 + Hcaptcha.js | 92 +++---- .../ConfirmHcaptcha.test.js.snap | 146 ++++++----- __tests__/__snapshots__/Hcaptcha.test.js.snap | 244 +++++++++++------- index.js | 7 +- 5 files changed, 288 insertions(+), 205 deletions(-) diff --git a/Hcaptcha.d.ts b/Hcaptcha.d.ts index e4bc0d5..0ba1c9c 100644 --- a/Hcaptcha.d.ts +++ b/Hcaptcha.d.ts @@ -32,6 +32,10 @@ type HcaptchaProps = { * Whether to show a loading indicator while the hCaptcha web content loads */ showLoading?: boolean; + /** + * Allow user to cancel hcaptcha during loading by touch loader overlay + */ + closableLoading?: boolean; /** * Color of the ActivityIndicator */ diff --git a/Hcaptcha.js b/Hcaptcha.js index 6b8463e..52594d9 100644 --- a/Hcaptcha.js +++ b/Hcaptcha.js @@ -1,6 +1,6 @@ -import React, { useMemo, useCallback, useRef } from 'react'; +import React, { useMemo, useCallback, useRef, useState } from 'react'; import WebView from 'react-native-webview'; -import { Linking, StyleSheet, View, ActivityIndicator } from 'react-native'; +import { Linking, StyleSheet, View, ActivityIndicator, TouchableWithoutFeedback } from 'react-native'; import ReactNativeVersion from 'react-native/Libraries/Core/ReactNativeVersion'; import md5 from './md5'; @@ -48,6 +48,7 @@ const buildHcaptchaApiUrl = (jsSrc, siteKey, hl, theme, host, sentry, endpoint, * @param {*} url: base url * @param {*} languageCode: can be found at https://docs.hcaptcha.com/languages * @param {*} showLoading: loading indicator for webview till hCaptcha web content loads + * @param {*} closableLoading: allow user to cancel hcaptcha during loading by touch loader overlay * @param {*} loadingIndicatorColor: color for the ActivityIndicator * @param {*} backgroundColor: backgroundColor which can be injected into HTML to alter css backdrop colour * @param {string|object} theme: can be 'light', 'dark', 'contrast' or custom theme object @@ -70,6 +71,7 @@ const Hcaptcha = ({ url, languageCode, showLoading, + closableLoading, loadingIndicatorColor, backgroundColor, theme, @@ -86,6 +88,7 @@ const Hcaptcha = ({ }) => { const apiUrl = buildHcaptchaApiUrl(jsSrc, siteKey, languageCode, theme, host, sentry, endpoint, assethost, imghost, reportapi, orientation); const tokenTimeout = 120000; + const [isLoading, setIsLoading] = useState(true); if (theme && typeof theme === 'string') { theme = `"${theme}"`; @@ -128,7 +131,7 @@ const Hcaptcha = ({ var onloadCallback = function() { try { console.log("challenge onload starting"); - hcaptcha.render("submit", getRenderConfig("${siteKey || ''}", ${theme}, "${size || 'invisible'}")); + hcaptcha.render("hcaptcha-container", getRenderConfig("${siteKey || ''}", ${theme}, "${size || 'invisible'}")); // have loaded by this point; render is sync. console.log("challenge render complete"); } catch (e) { @@ -150,6 +153,7 @@ const Hcaptcha = ({ window.ReactNativeWebView.postMessage("cancel"); }; var onOpen = function() { + document.body.style.backgroundColor = '${backgroundColor}'; window.ReactNativeWebView.postMessage("open"); console.log("challenge opened"); }; @@ -185,21 +189,20 @@ const Hcaptcha = ({ }; - -
+ +
`, [siteKey, backgroundColor, theme, debugInfo] ); // This shows ActivityIndicator till webview loads hCaptcha images - const renderLoading = useCallback( - () => ( - + const renderLoading = () => ( + closableLoading && onMessage({ nativeEvent: { data: 'cancel' } })}> + - ), - [loadingIndicatorColor] + ); const webViewRef = useRef(null); @@ -211,47 +214,46 @@ const Hcaptcha = ({ }; return ( - { - if (event.url.slice(0, 24) === 'https://www.hcaptcha.com') { - Linking.openURL(event.url); - return false; - } - return true; - }} - mixedContentMode={'always'} - onMessage={(e) => { - e.reset = reset; - if (e.nativeEvent.data.length > 16) { - const expiredTokenTimerId = setTimeout(() => onMessage({ nativeEvent: { data: 'expired' }, reset }), tokenTimeout); - e.markUsed = () => clearTimeout(expiredTokenTimerId); - } - onMessage(e); - }} - javaScriptEnabled - injectedJavaScript={patchPostMessageJsCode} - automaticallyAdjustContentInsets - style={[{ backgroundColor: 'transparent', width: '100%' }, style]} - source={{ - html: generateTheWebViewContent, - baseUrl: `${url}`, - }} - renderLoading={renderLoading} - startInLoadingState={showLoading} - /> + + { + if (event.url.slice(0, 24) === 'https://www.hcaptcha.com') { + Linking.openURL(event.url); + return false; + } + return true; + }} + mixedContentMode={'always'} + onMessage={(e) => { + e.reset = reset; + if (e.nativeEvent.data === 'open') { + setIsLoading(false); + } else if (e.nativeEvent.data.length > 16) { + const expiredTokenTimerId = setTimeout(() => onMessage({ nativeEvent: { data: 'expired' }, reset }), tokenTimeout); + e.markUsed = () => clearTimeout(expiredTokenTimerId); + } + onMessage(e); + }} + javaScriptEnabled + injectedJavaScript={patchPostMessageJsCode} + automaticallyAdjustContentInsets + style={[{ backgroundColor: 'transparent', width: '100%' }, style]} + source={{ + html: generateTheWebViewContent, + baseUrl: `${url}`, + }} + /> + {showLoading && isLoading && renderLoading()} + ); }; const styles = StyleSheet.create({ loadingOverlay: { - bottom: 0, + ...StyleSheet.absoluteFillObject, justifyContent: 'center', - left: 0, - position: 'absolute', - right: 0, - top: 0, }, }); diff --git a/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap b/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap index ea8b473..e03eb28 100644 --- a/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap +++ b/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap @@ -39,9 +39,16 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with all props 1 ] } > - + + javaScriptEnabled={true} + mixedContentMode="always" + onMessage={[Function]} + onShouldStartLoadWithRequest={[Function]} + originWhitelist={ + [ + "*", + ] + } + source={ + { + "baseUrl": "https://hcaptcha.com", + "html": " @@ -73,12 +79,12 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with all props 1 - + - -
+ +
", + } } - } - startInLoadingState={false} - style={ - [ - { - "backgroundColor": "transparent", - "width": "100%", - }, - undefined, - ] - } - /> + style={ + [ + { + "backgroundColor": "transparent", + "width": "100%", + }, + undefined, + ] + } + /> +
`; @@ -195,9 +202,16 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with minimum pro ] } > - + + javaScriptEnabled={true} + mixedContentMode="always" + onMessage={[Function]} + onShouldStartLoadWithRequest={[Function]} + originWhitelist={ + [ + "*", + ] + } + source={ + { + "baseUrl": "https://hcaptcha.com", + "html": " @@ -229,12 +242,12 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with minimum pro - + - -
+ +
", + } } - } - startInLoadingState={false} - style={ - [ - { - "backgroundColor": "transparent", - "width": "100%", - }, - undefined, - ] - } - /> + style={ + [ + { + "backgroundColor": "transparent", + "width": "100%", + }, + undefined, + ] + } + /> + `; diff --git a/__tests__/__snapshots__/Hcaptcha.test.js.snap b/__tests__/__snapshots__/Hcaptcha.test.js.snap index 09e3405..d1e2d12 100644 --- a/__tests__/__snapshots__/Hcaptcha.test.js.snap +++ b/__tests__/__snapshots__/Hcaptcha.test.js.snap @@ -1,9 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Hcaptcha snapshot tests renders Hcaptcha with all props 1`] = ` - + + javaScriptEnabled={true} + mixedContentMode="always" + onMessage={[Function]} + onShouldStartLoadWithRequest={[Function]} + originWhitelist={ + [ + "*", + ] + } + source={ + { + "baseUrl": "https://hcaptcha.com", + "html": " @@ -40,7 +46,7 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with all props 1`] = ` var onloadCallback = function() { try { console.log("challenge onload starting"); - hcaptcha.render("submit", getRenderConfig("00000000-0000-0000-0000-000000000000", "contrast", "normal")); + hcaptcha.render("hcaptcha-container", getRenderConfig("00000000-0000-0000-0000-000000000000", "contrast", "normal")); // have loaded by this point; render is sync. console.log("challenge render complete"); } catch (e) { @@ -62,6 +68,7 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with all props 1`] = ` window.ReactNativeWebView.postMessage("cancel"); }; var onOpen = function() { + document.body.style.backgroundColor = 'rgba(0.1, 0.1, 0.1, 0.4)'; window.ReactNativeWebView.postMessage("open"); console.log("challenge opened"); }; @@ -97,29 +104,71 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with all props 1`] = ` }; - -
+ +
", + } } - } - startInLoadingState={true} - style={ - [ + style={ + [ + { + "backgroundColor": "transparent", + "width": "100%", + }, + undefined, + ] + } + /> + + "busy": undefined, + "checked": undefined, + "disabled": undefined, + "expanded": undefined, + "selected": undefined, + } + } + accessible={true} + focusable={true} + onClick={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + style={ + { + "bottom": 0, + "justifyContent": "center", + "left": 0, + "position": "absolute", + "right": 0, + "top": 0, + } + } + > + + + `; exports[`Hcaptcha snapshot tests renders Hcaptcha with minimum props 1`] = ` - + + javaScriptEnabled={true} + mixedContentMode="always" + onMessage={[Function]} + onShouldStartLoadWithRequest={[Function]} + originWhitelist={ + [ + "*", + ] + } + source={ + { + "baseUrl": "https://hcaptcha.com", + "html": " @@ -156,7 +204,7 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with minimum props 1`] = ` var onloadCallback = function() { try { console.log("challenge onload starting"); - hcaptcha.render("submit", getRenderConfig("", undefined, "invisible")); + hcaptcha.render("hcaptcha-container", getRenderConfig("", undefined, "invisible")); // have loaded by this point; render is sync. console.log("challenge render complete"); } catch (e) { @@ -178,6 +226,7 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with minimum props 1`] = ` window.ReactNativeWebView.postMessage("cancel"); }; var onOpen = function() { + document.body.style.backgroundColor = 'undefined'; window.ReactNativeWebView.postMessage("open"); console.log("challenge opened"); }; @@ -213,28 +262,36 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with minimum props 1`] = ` }; - -
+ +
", + } } - } - style={ - [ - { - "backgroundColor": "transparent", - "width": "100%", - }, - undefined, - ] - } -/> + style={ + [ + { + "backgroundColor": "transparent", + "width": "100%", + }, + undefined, + ] + } + /> + `; exports[`Hcaptcha snapshot tests test debug 1`] = ` - + + javaScriptEnabled={true} + mixedContentMode="always" + onMessage={[Function]} + onShouldStartLoadWithRequest={[Function]} + originWhitelist={ + [ + "*", + ] + } + source={ + { + "baseUrl": "https://hcaptcha.com", + "html": " @@ -271,7 +327,7 @@ exports[`Hcaptcha snapshot tests test debug 1`] = ` var onloadCallback = function() { try { console.log("challenge onload starting"); - hcaptcha.render("submit", getRenderConfig("00000000-0000-0000-0000-000000000000", undefined, "invisible")); + hcaptcha.render("hcaptcha-container", getRenderConfig("00000000-0000-0000-0000-000000000000", undefined, "invisible")); // have loaded by this point; render is sync. console.log("challenge render complete"); } catch (e) { @@ -293,6 +349,7 @@ exports[`Hcaptcha snapshot tests test debug 1`] = ` window.ReactNativeWebView.postMessage("cancel"); }; var onOpen = function() { + document.body.style.backgroundColor = 'undefined'; window.ReactNativeWebView.postMessage("open"); console.log("challenge opened"); }; @@ -328,20 +385,21 @@ exports[`Hcaptcha snapshot tests test debug 1`] = ` }; - -
+ +
", + } } - } - style={ - [ - { - "backgroundColor": "transparent", - "width": "100%", - }, - undefined, - ] - } -/> + style={ + [ + { + "backgroundColor": "transparent", + "width": "100%", + }, + undefined, + ] + } + /> + `; diff --git a/index.js b/index.js index 0298b47..0759e6b 100644 --- a/index.js +++ b/index.js @@ -31,6 +31,7 @@ class ConfirmHcaptcha extends PureComponent { orientation, onMessage, showLoading, + closableLoading, backgroundColor, loadingIndicatorColor, theme, @@ -61,7 +62,7 @@ class ConfirmHcaptcha extends PureComponent { hasBackdrop={!passiveSiteKey && hasBackdrop} coverScreen={!passiveSiteKey} > - + @@ -114,6 +117,7 @@ ConfirmHcaptcha.propTypes = { orientation: PropTypes.string, backgroundColor: PropTypes.string, showLoading: PropTypes.bool, + closableLoading: PropTypes.bool, loadingIndicatorColor: PropTypes.string, theme: PropTypes.string, rqdata: PropTypes.string, @@ -132,6 +136,7 @@ ConfirmHcaptcha.defaultProps = { size: 'invisible', passiveSiteKey: false, showLoading: false, + closableLoading: false, orientation: 'portrait', backgroundColor: 'rgba(0, 0, 0, 0.3)', loadingIndicatorColor: null, From 9a8b4279f5dac4463c89c071500841ca70a6d319 Mon Sep 17 00:00:00 2001 From: Alexande B Date: Sun, 3 Nov 2024 22:07:34 +0100 Subject: [PATCH 2/5] fix: yarn install on CI with YARN_ENABLE_IMMUTABLE_INSTALLS:false --- .github/workflows/tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e6e50ea..8716634 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -51,6 +51,8 @@ jobs: # npm install -g ${{ matrix.pm }} react-native npm run example -- --pm ${{ matrix.pm }} working-directory: react-native-hcaptcha + env: + YARN_ENABLE_IMMUTABLE_INSTALLS: false - id: rn-version working-directory: react-native-hcaptcha-example run: | From 21742cc6619b96ff7a8a549b96e838da5812f1b7 Mon Sep 17 00:00:00 2001 From: Alexande B Date: Sun, 3 Nov 2024 22:30:17 +0100 Subject: [PATCH 3/5] chore: bump to 1.8.2 --- __tests__/__snapshots__/ConfirmHcaptcha.test.js.snap | 2 +- __tests__/__snapshots__/Hcaptcha.test.js.snap | 4 ++-- package-lock.json | 4 ++-- package.json | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap b/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap index e03eb28..cec807a 100644 --- a/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap +++ b/__tests__/__snapshots__/ConfirmHcaptcha.test.js.snap @@ -77,7 +77,7 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with all props 1