diff --git a/Hcaptcha.d.ts b/Hcaptcha.d.ts
index 8268b70..7a1d8ec 100644
--- a/Hcaptcha.d.ts
+++ b/Hcaptcha.d.ts
@@ -13,6 +13,14 @@ export type HcaptchaProps = {
* The callback function that runs after receiving a response, error, or when user cancels.
*/
onMessage: (event: CustomWebViewMessageEvent) => void;
+ /**
+ * Runs after the hCaptcha API loads.
+ */
+ onLoad?: () => void;
+ /**
+ * Runs when hCaptcha is ready to execute.
+ */
+ onReady?: () => void;
/**
* The size of the checkbox.
*/
diff --git a/Hcaptcha.js b/Hcaptcha.js
index f4cb9d3..dca6269 100644
--- a/Hcaptcha.js
+++ b/Hcaptcha.js
@@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
+import hCaptchaLoaderInlineScript from '@hcaptcha/loader/inline';
import WebView from 'react-native-webview';
import { ActivityIndicator, Linking, Platform, StyleSheet, TouchableWithoutFeedback, View } from 'react-native';
import ReactNativeVersion from 'react-native/Libraries/Core/ReactNativeVersion';
@@ -26,6 +27,7 @@ const patchPostMessageJsCode = `(${String(function () {
window.ReactNativeWebView.postMessage = patchedPostMessage;
})})();`;
+const HCAPTCHA_LOAD_EVENT = '__hcaptcha_load__';
const HCAPTCHA_READY_EVENT = '__hcaptcha_ready__';
const serializeForInlineScript = (value) =>
@@ -135,28 +137,45 @@ const buildVerifyData = ({
const buildVerifyInjectionScript = (payload, resetFirst = false) =>
`try { ${resetFirst ? 'reset(); ' : ''}setData(${serializeForInlineScript(payload)}); execute(); } catch (e) { window.ReactNativeWebView.postMessage((e && e.name) || 'error'); } true;`;
-const buildHcaptchaApiUrl = (jsSrc, siteKey, hl, theme, host, sentry, endpoint, assethost, imghost, reportapi, orientation) => {
- var url = `${jsSrc || 'https://hcaptcha.com/1/api.js'}?render=explicit&onload=onloadCallback`;
-
- let effectiveHost;
+const getHcaptchaHost = (host, siteKey) => {
if (host) {
- effectiveHost = encodeURIComponent(host);
+ return host;
+ } else if (siteKey) {
+ return `${siteKey}.react-native.hcaptcha.com`;
} else {
- effectiveHost = (siteKey || 'missing-sitekey') + '.react-native.hcaptcha.com';
- }
-
- for (let [key, value] of Object.entries({ host: effectiveHost, hl, custom: typeof theme === 'object', sentry, endpoint, assethost, imghost, reportapi, orientation })) {
- if (value) {
- url += `&${key}=${encodeURIComponent(value)}`;
- }
+ return 'missing-sitekey.react-native.hcaptcha.com';
}
-
- return url;
};
+const buildHcaptchaLoaderConfig = ({
+ scriptSource,
+ siteKey,
+ hl,
+ theme,
+ host,
+ sentry,
+ endpoint,
+ assethost,
+ imghost,
+ reportapi,
+}) => ({
+ scriptSource,
+ render: 'explicit',
+ host: getHcaptchaHost(host, siteKey),
+ hl,
+ custom: typeof theme === 'object',
+ sentry: Boolean(sentry),
+ endpoint,
+ assethost,
+ imghost,
+ reportapi,
+});
+
/**
*
* @param {*} onMessage: callback after receiving response, error, or when user cancels
+ * @param {function} onLoad: callback after the hCaptcha API loads
+ * @param {function} onReady: callback when hCaptcha is ready to execute
* @param {*} siteKey: your hCaptcha sitekey
* @param {string} size: The size of the widget, can be 'invisible', 'compact' or 'normal'. 'checkbox' is kept as a legacy alias for 'normal'. Default: 'invisible'
* @param {*} style: custom style
@@ -184,6 +203,8 @@ const buildHcaptchaApiUrl = (jsSrc, siteKey, hl, theme, host, sentry, endpoint,
*/
const Hcaptcha = ({
onMessage,
+ onLoad,
+ onReady,
size,
siteKey,
style,
@@ -217,9 +238,20 @@ const Hcaptcha = ({
const hasJourneyConsumerRef = useRef(false);
const normalizedTheme = useMemo(() => normalizeTheme(theme), [theme]);
const normalizedSize = useMemo(() => normalizeSize(size), [size]);
- const apiUrl = useMemo(
- () => buildHcaptchaApiUrl(jsSrc, siteKey, languageCode, normalizedTheme, host, sentry, endpoint, assethost, imghost, reportapi, orientation),
- [jsSrc, siteKey, languageCode, normalizedTheme, host, sentry, endpoint, assethost, imghost, reportapi, orientation]
+ const loaderConfig = useMemo(
+ () => buildHcaptchaLoaderConfig({
+ scriptSource: jsSrc,
+ siteKey,
+ hl: languageCode,
+ theme: normalizedTheme,
+ host,
+ sentry,
+ endpoint,
+ assethost,
+ imghost,
+ reportapi,
+ }),
+ [jsSrc, siteKey, languageCode, normalizedTheme, host, sentry, endpoint, assethost, imghost, reportapi]
);
const debugInfo = useMemo(
@@ -229,17 +261,18 @@ const Hcaptcha = ({
const serializedWebViewConfig = useMemo(
() => serializeForInlineScript({
- apiUrl,
+ loaderConfig,
backgroundColor: backgroundColor ?? '',
debugInfo,
phoneNumber: phoneNumber ?? null,
phonePrefix: phonePrefix ?? null,
+ orientation: orientation ?? null,
rqdata: rqdata ?? null,
siteKey: siteKey || '',
size: normalizedSize,
theme: normalizedTheme,
}),
- [apiUrl, backgroundColor, debugInfo, normalizedSize, normalizedTheme, phoneNumber, phonePrefix, rqdata, siteKey]
+ [loaderConfig, backgroundColor, debugInfo, normalizedSize, normalizedTheme, orientation, phoneNumber, phonePrefix, rqdata, siteKey]
);
const generateTheWebViewContent = useMemo(
@@ -254,13 +287,19 @@ const Hcaptcha = ({
var hcaptchaConfig = ${serializedWebViewConfig};
Object.entries(hcaptchaConfig.debugInfo || {}).forEach(function (entry) { window[entry[0]] = entry[1] });
+