Monorepo for Tangled
Something went wrong. Try again.
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100<!-- One explicit-render script per session; mounts share its load promise so concurrent instances can't clobber each other's onload. --><script module lang="ts"> // turnstile ships no typings; this is the surface we use declare global { interface Window { turnstile?: { render: ( container: HTMLElement, options: { sitekey: string; size?: "normal" | "compact" | "flexible"; theme?: "light" | "dark" | "auto"; callback: (token: string) => void; "expired-callback"?: () => void; "error-callback"?: () => void; } ) => string; remove: (widgetId: string) => void; }; } }
let script: HTMLScriptElement | undefined; let loadPromise: Promise<void> | undefined;
const ensureLoaded = (): Promise<void> => { if (window.turnstile) return Promise.resolve(); if (loadPromise) return loadPromise; script = document.createElement("script"); script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"; script.async = true; loadPromise = new Promise<void>((resolve, reject) => { script!.addEventListener("load", () => resolve()); script!.addEventListener("error", () => reject(new Error("turnstile script failed to load")) ); }); document.head.appendChild(script); return loadPromise; };</script>
<script lang="ts"> import { onMount } from "svelte";
interface Props { siteKey: string; onToken: (token: string) => void; /** called when an issued token expires or the challenge errors — the holder must drop it */ onExpire?: () => void; }
let { siteKey, onToken, onExpire }: Props = $props();
let container = $state<HTMLDivElement>(); let widgetId: string | undefined; let failed = $state(false);
onMount(() => { let cancelled = false; ensureLoaded() .then(() => { if (cancelled) return; const el = container; const api = window.turnstile; if (!el || !api) return; const dark = getComputedStyle(el).colorScheme === "dark"; try { widgetId = api.render(el, { sitekey: siteKey, size: "flexible", theme: dark ? "dark" : "light", callback: onToken, "expired-callback": () => onExpire?.(), "error-callback": () => onExpire?.() }); } catch { failed = true; } }) .catch(() => { if (!cancelled) failed = true; });
return () => { cancelled = true; if (widgetId) window.turnstile?.remove(widgetId); }; });</script>
{#if failed} <p class="typography-paragraph-small text-foreground-danger" role="alert"> Couldn't load the verification widget. Check your connection and try refreshing. </p>{:else} <div bind:this={container} class="min-h-16 w-full"></div>{/if}