Skip to content

Commit eed4b09

Browse files
committed
fix(clerk-js): Add support for loading Turnstile from Cloudflare host (#4321)
1 parent d8fdebb commit eed4b09

File tree

2 files changed

+29
-10
lines changed

2 files changed

+29
-10
lines changed

.changeset/odd-squids-dress.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@clerk/clerk-js": patch
3+
---
4+
5+
Improve bot detection by loading the Turnstile SDK directly from CloudFlare.
6+
7+
If loading fails due to CSP rules, load it through FAPI instead.

packages/clerk-js/src/utils/captcha/turnstile.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { CaptchaWidgetType } from '@clerk/types';
33

44
import { CAPTCHA_ELEMENT_ID, CAPTCHA_INVISIBLE_CLASSNAME } from './constants';
55

6+
const CLOUDFLARE_TURNSTILE_ORIGINAL_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
7+
68
interface RenderOptions {
79
/**
810
* Every widget has a sitekey. This sitekey is associated with the corresponding widget configuration and is created upon the widget creation.
@@ -69,21 +71,31 @@ export const shouldRetryTurnstileErrorCode = (errorCode: string) => {
6971
return !!codesWithRetries.find(w => errorCode.startsWith(w));
7072
};
7173

72-
async function loadCaptcha(url: string) {
74+
async function loadCaptcha(fallbackUrl: string) {
7375
if (!window.turnstile) {
74-
try {
75-
await loadScript(url, { defer: true });
76-
} catch {
77-
// Rethrow with specific message
78-
console.error('Clerk: Failed to load the CAPTCHA script from the URL: ', url);
79-
throw {
80-
captchaError: 'captcha_script_failed_to_load',
81-
};
82-
}
76+
await loadCaptchaFromCloudflareURL()
77+
.catch(() => loadCaptchaFromFAPIProxiedURL(fallbackUrl))
78+
.catch(() => {
79+
throw { captchaError: 'captcha_script_failed_to_load' };
80+
});
8381
}
8482
return window.turnstile;
8583
}
8684

85+
async function loadCaptchaFromCloudflareURL() {
86+
return await loadScript(CLOUDFLARE_TURNSTILE_ORIGINAL_URL, { defer: true });
87+
}
88+
89+
async function loadCaptchaFromFAPIProxiedURL(fallbackUrl: string) {
90+
try {
91+
return await loadScript(fallbackUrl, { defer: true });
92+
} catch (err) {
93+
// Rethrow with specific message
94+
console.error('Clerk: Failed to load the CAPTCHA script from the URL: ', fallbackUrl);
95+
throw err;
96+
}
97+
}
98+
8799
/*
88100
* How this function works:
89101
* The widgetType is either 'invisible' or 'smart'.

0 commit comments

Comments
 (0)