Skip to content

Commit dd09d38

Browse files
braden-clerkclaude
andcommitted
fix(clerk-js): Add retry logic and race condition fixes for Cloudflare captcha error 200100
This fixes Cloudflare Turnstile error 200100 ("Widget not found") which occurs when the captcha container element isn't available during rendering. Changes: - Add '200' to shouldRetryTurnstileErrorCode to retry all 200xxx errors (including 200100) - Add waitForElement() call before captcha.render() to verify container exists - Update smart widget initialization to use waitForElement() instead of getElementById() - Add test coverage for error codes 200, 200100, and 200xxx These changes prevent race conditions where DOM elements are removed or not ready between checks and render calls, particularly during React/framework re-renders. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent aa2d3b5 commit dd09d38

2 files changed

Lines changed: 14 additions & 4 deletions

File tree

packages/clerk-js/src/utils/__tests__/captcha.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ describe('shouldRetryTurnstileErrorCode', () => {
1414
['104xxx', true],
1515
['106xxx', true],
1616
['110600', true],
17+
['200', true],
18+
['200100', true],
19+
['200xxx', true],
1720
['300xxx', true],
1821
['600xxx', true],
19-
['200010', false],
2022
['100405', false],
2123
['105001', false],
2224
['110430', false],

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ declare global {
2323
}
2424

2525
export const shouldRetryTurnstileErrorCode = (errorCode: string) => {
26-
const codesWithRetries = ['crashed', 'undefined_error', '102', '103', '104', '106', '110600', '300', '600'];
26+
const codesWithRetries = ['crashed', 'undefined_error', '102', '103', '104', '106', '110600', '200', '300', '600'];
2727
return !!codesWithRetries.find(w => errorCode.startsWith(w));
2828
};
2929

@@ -117,7 +117,8 @@ export const getTurnstileToken = async (opts: CaptchaOptions) => {
117117

118118
// smart widget with container provided by user
119119
if (!widgetContainerQuerySelector && widgetType === 'smart') {
120-
const visibleDiv = document.getElementById(CAPTCHA_ELEMENT_ID);
120+
// Use waitForElement to ensure the element is ready, similar to modal approach
121+
const visibleDiv = await waitForElement(`#${CAPTCHA_ELEMENT_ID}`).catch(() => null);
121122
if (visibleDiv) {
122123
captchaTypeUsed = 'smart';
123124
captchaWidgetType = 'smart';
@@ -147,8 +148,15 @@ export const getTurnstileToken = async (opts: CaptchaOptions) => {
147148
}
148149

149150
const handleCaptchaTokenGeneration = async (): Promise<[string, string]> => {
150-
return new Promise((resolve, reject) => {
151+
return new Promise(async (resolve, reject) => {
151152
try {
153+
// Re-verify element exists right before render to prevent 200100 errors
154+
const containerElement = await waitForElement(widgetContainerQuerySelector);
155+
if (!containerElement) {
156+
reject(['Widget container element not found', undefined]);
157+
return;
158+
}
159+
152160
const id = captcha.render(widgetContainerQuerySelector, {
153161
sitekey: turnstileSiteKey,
154162
appearance: 'interaction-only',

0 commit comments

Comments
 (0)