Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Hcaptcha.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ type HcaptchaProps = {
* Default: portrait
*/
orientation?: 'portrait' | 'landscape';
/**
* Optional phone country calling code (without '+'), e.g., "44".
* Used in MFA flows.
*/
phonePrefix?: string;
/**
* Optional full phone number in E.164 format ("+44123..."), for use in MFA.
*/
phoneNumber?: string;
}

interface CustomWebViewMessageEvent extends WebViewMessageEvent {
Expand Down
19 changes: 16 additions & 3 deletions Hcaptcha.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const buildHcaptchaApiUrl = (jsSrc, siteKey, hl, theme, host, sentry, endpoint,
* @param {string} host: hCaptcha SDK host identifier. null value means that it will be generated by SDK
* @param {object} debug: debug information
* @param {string} orientation: hCaptcha challenge orientation
* @param {string} phonePrefix: Optional phone country calling code (without '+'), e.g., "44". Used in MFA flows.
* @param {string} phoneNumber: Optional full phone number in E.164 format ("+44123..."), for use in MFA.
*/
const Hcaptcha = ({
onMessage,
Expand All @@ -85,6 +87,8 @@ const Hcaptcha = ({
host,
debug,
orientation,
phonePrefix,
phoneNumber,
}) => {
const apiUrl = buildHcaptchaApiUrl(jsSrc, siteKey, languageCode, theme, host, sentry, endpoint, assethost, imghost, reportapi, orientation);
const tokenTimeout = 120000;
Expand Down Expand Up @@ -189,10 +193,19 @@ const Hcaptcha = ({
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = ${rqdata};
const phonePrefix = ${phonePrefix || 'null'};
const phoneNumber = ${phoneNumber || 'null'};

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand All @@ -202,7 +215,7 @@ const Hcaptcha = ({
<div id="hcaptcha-container"></div>
</body>
</html>`,
[debugInfo, apiUrl, siteKey, theme, size, backgroundColor, rqdata]
[debugInfo, apiUrl, siteKey, theme, size, backgroundColor, rqdata, phonePrefix, phoneNumber]
);

useEffect(() => {
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ Otherwise, you should pass in the preferred device locale, e.g. fetched from `ge
- You can `import Hcaptcha from '@hcaptcha/react-native-hcaptcha/Hcaptcha';` to customize the UI yourself.
- hCaptcha loading is restricted to a 15-second timeout; an `error` will be sent via `onMessage` if it fails to load due to network issues.

## MFA Phone Support

The SDK supports phone prefix and phone number parameters for MFA (Multi-Factor Authentication) flows. You can pass these parameters as props:

```js
// Using phone prefix (country code without '+')
<ConfirmHcaptcha
siteKey="your-site-key"
phonePrefix="44"
onMessage={onMessage}
/>

// Using phone number (full E.164 format)
<ConfirmHcaptcha
siteKey="your-site-key"
phoneNumber="+44123456789"
onMessage={onMessage}
/>
```

## Properties

| **Name** | **Type** | **Description** |
Expand All @@ -142,7 +162,7 @@ Otherwise, you should pass in the preferred device locale, e.g. fetched from `ge
| loadingIndicatorColor | string | Color of the ActivityIndicator |
| backgroundColor | string | The background color code that will be applied to the main HTML element |
| theme | string\|object | The theme can be 'light', 'dark', 'contrast' or a custom theme object (see Enterprise docs) |
| rqdata | string | Hcaptcha execution options (see Enterprise docs) |
| rqdata | string | **Deprecated**: Use `rqdata` in `HCaptchaVerifyParams` instead. Will be removed in future releases. See Enterprise docs. |
| sentry | boolean | sentry error reporting (see Enterprise docs) |
| jsSrc | string | The url of api.js. Default: https://js.hcaptcha.com/1/api.js (Override only if using first-party hosting feature.) |
| endpoint | string | Point hCaptcha JS Ajax Requests to alternative API Endpoint. Default: https://api.hcaptcha.com (Override only if using first-party hosting feature.) |
Expand All @@ -156,6 +176,8 @@ Otherwise, you should pass in the preferred device locale, e.g. fetched from `ge
| passiveSiteKey _(modal component only)_ | boolean | Indicates whether the passive mode is enabled; when true, the modal won't be shown at all |
| hasBackdrop _(modal component only)_ | boolean | Defines if the modal backdrop is shown (true by default). If `hasBackdrop=false`, `backgroundColor` will apply only after the hCaptcha visual challenge is presented. |
| orientation | string | This specifies the "orientation" of the challenge. It can be `portrait`, `landscape`. Default: `portrait` |
| phonePrefix | string | Optional phone country calling code (without '+'), e.g., "44". Used in MFA flows. |
| phoneNumber | string | Optional full phone number in E.164 format ("+44123..."), for use in MFA. |


## Status
Expand Down
43 changes: 35 additions & 8 deletions __tests__/__snapshots__/ConfirmHcaptcha.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with all props 1
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://all.props/api-endpoint?render=explicit&onload=onloadCallback&host=all-props-host&hl=en&sentry=true&endpoint=https%3A%2F%2Fall.props%2Fendpoint&assethost=https%3A%2F%2Fall.props%2Fassethost&imghost=https%3A%2F%2Fall.props%2Fimghost&reportapi=https%3A%2F%2Fall.props%2Freportapi&orientation=portrait" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -138,10 +138,19 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with all props 1
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = "{"some": "data"}";
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -306,10 +315,19 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha with minimum pro
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = null;
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -416,7 +434,7 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha without safe are
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://all.props/api-endpoint?render=explicit&onload=onloadCallback&host=all-props-host&hl=en&sentry=true&endpoint=https%3A%2F%2Fall.props%2Fendpoint&assethost=https%3A%2F%2Fall.props%2Fassethost&imghost=https%3A%2F%2Fall.props%2Fimghost&reportapi=https%3A%2F%2Fall.props%2Freportapi&orientation=portrait" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -472,10 +490,19 @@ exports[`ConfirmHcaptcha snapshot tests renders ConfirmHcaptcha without safe are
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = "{"some": "data"}";
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down
88 changes: 71 additions & 17 deletions __tests__/__snapshots__/Hcaptcha.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ exports[`Hcaptcha snapshot tests Theme test object 1`] = `
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://hcaptcha.com/1/api.js?render=explicit&onload=onloadCallback&host=00000000-0000-0000-0000-000000000000.react-native.hcaptcha.com&hl=en&custom=true" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -100,10 +100,19 @@ exports[`Hcaptcha snapshot tests Theme test object 1`] = `
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = undefined;
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -172,7 +181,7 @@ exports[`Hcaptcha snapshot tests Theme test string 1`] = `
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://hcaptcha.com/1/api.js?render=explicit&onload=onloadCallback&host=00000000-0000-0000-0000-000000000000.react-native.hcaptcha.com&hl=en" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -228,10 +237,19 @@ exports[`Hcaptcha snapshot tests Theme test string 1`] = `
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = undefined;
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -300,7 +318,7 @@ exports[`Hcaptcha snapshot tests Theme test undefined 1`] = `
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://hcaptcha.com/1/api.js?render=explicit&onload=onloadCallback&host=00000000-0000-0000-0000-000000000000.react-native.hcaptcha.com&hl=en" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -356,10 +374,19 @@ exports[`Hcaptcha snapshot tests Theme test undefined 1`] = `
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = undefined;
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -428,7 +455,7 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with all props 1`] = `
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://all.props/api-endpoint?render=explicit&onload=onloadCallback&host=all-props-host&hl=fr&endpoint=https%3A%2F%2Fall.props%2Fendpoint&assethost=https%3A%2F%2Fall.props%2Fassethost&imghost=https%3A%2F%2Fall.props%2Fimghost&reportapi=https%3A%2F%2Fall.props%2Freportapi" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -484,10 +511,19 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with all props 1`] = `
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = "{}";
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -591,7 +627,7 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with debug 1`] = `
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script type="text/javascript">
Object.entries({"a":1,"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_0_5":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
Object.entries({"a":1,"rnver_0_0_0":true,"dep_mocked-md5":true,"sdk_2_1_0":true}).forEach(function (entry) { window[entry[0]] = entry[1] })
</script>
<script src="https://hcaptcha.com/1/api.js?render=explicit&onload=onloadCallback&host=00000000-0000-0000-0000-000000000000.react-native.hcaptcha.com&hl=en" async defer></script>
<script type="text/javascript">
Expand Down Expand Up @@ -647,10 +683,19 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with debug 1`] = `
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = undefined;
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down Expand Up @@ -775,10 +820,19 @@ exports[`Hcaptcha snapshot tests renders Hcaptcha with minimum props 1`] = `
return config;
};
const getExecuteOpts = function() {
var opts;
var opts = {};
const rqdata = undefined;
const phonePrefix = null;
const phoneNumber = null;

if (rqdata) {
opts = {"rqdata": rqdata};
opts.rqdata = rqdata;
}
if (phonePrefix) {
opts.mfa_phoneprefix = phonePrefix;
}
if (phoneNumber) {
opts.mfa_phone = phoneNumber;
}
return opts;
};
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hcaptcha/react-native-hcaptcha",
"version": "2.0.5",
"version": "2.1.0",
"description": "hCaptcha Library for React Native (both Android and iOS)",
"main": "index.js",
"scripts": {
Expand Down
Loading