turnstile.svelte RawHistoryFolder
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { fade } from 'svelte/transition';
4 import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public';
5 import type { Turnstile, TurnstileOptions } from '$lib/types/turnstile';
6
7 let isVerified = $state(false);
8
9 type OnVerifyCallback = (token: string) => void;
10 let { onVerify } = $props<{ onVerify: OnVerifyCallback }>();
11
12 let turnstileElement = $state<HTMLDivElement | null>(null);
13 let widgetId = '';
14
15 // Ensure site key is a string and trim any whitespace
16 const siteKey = String(PUBLIC_TURNSTILE_SITE_KEY).trim();
17
18 onMount(() => {
19 if (!window.turnstile) {
20 const script = document.createElement('script');
21 script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
22 script.async = true;
23 script.defer = true;
24 document.head.appendChild(script);
25
26 script.onload = initTurnstile;
27 } else {
28 initTurnstile();
29 }
30
31 return () => {
32 if (widgetId) {
33 window.turnstile?.remove(widgetId);
34 }
35 };
36 });
37
38 function initTurnstile() {
39 if (!turnstileElement || !window.turnstile) return;
40
41 // Validate site key is a non-empty string
42 if (!siteKey) {
43 console.error('Turnstile site key is missing');
44 return;
45 }
46
47 const options: TurnstileOptions = {
48 sitekey: siteKey,
49 callback: (token: string) => {
50 if (typeof onVerify === 'function') {
51 onVerify(token);
52 isVerified = true;
53 } else {
54 console.error('onVerify callback is not a function');
55 }
56 },
57 'refresh-expired': 'auto',
58 size: 'compact',
59 'error-callback': (error: unknown) => {
60 console.error('Turnstile error:', error);
61 isVerified = false; // Reset verification state on error
62 }
63 };
64
65 try {
66 widgetId = window.turnstile.render(turnstileElement, options);
67
68 // Add resize handler
69 const handleResize = () => scaleTurnstileToFit();
70 window.addEventListener('resize', handleResize);
71 // Initial scale
72 scaleTurnstileToFit();
73
74 } catch (error) {
75 console.error('Failed to render Turnstile:', error);
76 }
77 }
78
79 function scaleTurnstileToFit() {
80 const container = turnstileElement;
81 if (!container) return;
82
83 const iframe = container.querySelector('iframe');
84 if (!iframe) return;
85
86 const containerWidth = container.offsetWidth;
87 const scale = Math.min(1, containerWidth / 300); // 300px is approximate base width
88
89 iframe.style.transform = `scale(${scale})`;
90 iframe.style.transformOrigin = 'center center';
91 }
92</script>
93
94{#if !isVerified}
95 <div
96 class="turnstile-wrapper"
97 transition:fade={{ duration: 300 }}
98 >
99 <div bind:this={turnstileElement} class="turnstile-container" data-theme="light"></div>
100 </div>
101{/if}
102
103<style>
104 .turnstile-wrapper {
105 width: 100%;
106 display: flex;
107 justify-content: center;
108 align-items: center;
109 min-height: 65px; /* Approximate height for compact size */
110 overflow: hidden;
111 }
112
113 .turnstile-container {
114 display: flex;
115 justify-content: center;
116 align-items: center;
117 }
118
119 /* Handle iframe scaling */
120 :global(.turnstile-wrapper iframe) {
121 transition: transform 0.2s ease-in-out;
122 }
123</style>