Skip to content

Commit d129376

Browse files
committed
Flesh out turnstile fixes.
1 parent 418fa62 commit d129376

3 files changed

Lines changed: 38 additions & 16 deletions

File tree

web/src/flow/stages/captcha/CaptchaStage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ export class CaptchaStage
529529
const template = iframeTemplate(captchaElement, {
530530
challengeURL: challengeURL.toString(),
531531
theme: this.activeTheme,
532+
scriptOnLoad: !(controller instanceof TurnstileController),
532533
});
533534

534535
if (

web/src/flow/stages/captcha/controllers/turnstile.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
/* eslint-disable @typescript-eslint/triple-slash-reference */
22
/// <reference types="turnstile-types"/>
3-
import { ifPresent } from "#elements/utils/attributes";
4-
53
import { CaptchaController } from "#flow/stages/captcha/controllers/CaptchaController";
64

75
import { TurnstileObject } from "turnstile-types";
@@ -20,7 +18,16 @@ export class TurnstileController extends CaptchaController {
2018
public prepareURL = (): URL | null => {
2119
const input = this.host.challenge?.jsUrl;
2220

23-
return input && URL.canParse(input) ? new URL(input) : null;
21+
if (!input || !URL.canParse(input)) return null;
22+
23+
const url = new URL(input);
24+
25+
// Use explicit rendering to prevent Turnstile's 3-hour self-upgrade
26+
// from calling implicitRenderAll() and duplicating widgets.
27+
url.searchParams.set("render", "explicit");
28+
url.searchParams.set("onload", "onTurnstileReady");
29+
30+
return url;
2431
};
2532

2633
/**
@@ -33,25 +40,34 @@ export class TurnstileController extends CaptchaController {
3340
/**
3441
* Renders the Turnstile captcha frame.
3542
*
43+
* Uses explicit rendering to avoid Turnstile's self-upgrade mechanism
44+
* (every ~3 hours) from calling `implicitRenderAll()` and duplicating widgets.
45+
*
3646
* @remarks
3747
*
38-
* Turnstile will log a warning if the `data-language` attribute
48+
* Turnstile will log a warning if the `language` option
3949
* is not in lower-case format.
4050
*
4151
* @see {@link https://developers.cloudflare.com/turnstile/reference/supported-languages/ Turnstile Supported Languages}
4252
*/
4353
public interactive = () => {
44-
const languageTag = this.host.activeLanguageTag.toLowerCase();
54+
const siteKey = this.host.challenge?.siteKey ?? "";
55+
const theme = this.host.activeTheme;
56+
const language = this.host.activeLanguageTag.toLowerCase();
4557

46-
return html`<div
47-
id="ak-container"
48-
class="cf-turnstile"
49-
data-sitekey=${ifPresent(this.host.challenge?.siteKey)}
50-
data-theme=${this.host.activeTheme}
51-
data-callback="callback"
52-
data-size="flexible"
53-
data-language=${ifPresent(languageTag)}
54-
></div>`;
58+
return html`<div id="ak-container"></div>
59+
<script>
60+
function onTurnstileReady() {
61+
turnstile.render("#ak-container", {
62+
sitekey: "${siteKey}",
63+
theme: "${theme}",
64+
language: "${language}",
65+
size: "flexible",
66+
callback,
67+
});
68+
loadListener();
69+
}
70+
</script>`;
5571
};
5672

5773
public refreshInteractive = async () => {

web/src/flow/stages/captcha/shared.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export function themeMeta(theme: ResolvedUITheme) {
2525
export interface IFrameTemplateInit {
2626
challengeURL: URL | string;
2727
theme: ResolvedUITheme;
28+
/**
29+
* If `true`, the script element will fire `loadListener()` on load.
30+
* Defaults to `true`.
31+
*/
32+
scriptOnLoad?: boolean;
2833
}
2934

3035
/**
@@ -37,7 +42,7 @@ export interface IFrameTemplateInit {
3742
*/
3843
export function iframeTemplate(
3944
children: TemplateResult,
40-
{ challengeURL, theme }: IFrameTemplateInit,
45+
{ challengeURL, theme, scriptOnLoad = true }: IFrameTemplateInit,
4146
) {
4247
return createDocumentTemplate({
4348
head: html`
@@ -90,7 +95,7 @@ export function iframeTemplate(
9095
}
9196
</style>
9297
${children}
93-
<script onload="loadListener()" src="${challengeURL.toString()}"></script>
98+
<script ${scriptOnLoad ? 'onload="loadListener()"' : ""} src="${challengeURL.toString()}"></script>
9499
`,
95100
});
96101
}

0 commit comments

Comments
 (0)