turnstile.svelte RawHistoryFolder
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { fade } from 'svelte/transition';
4 import { env } from '$env/dynamic/public';
5 import type { 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(env.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 } catch (error) {
74 console.error('Failed to render Turnstile:', error);
75 }
76 }
77
78 function scaleTurnstileToFit() {
79 const container = turnstileElement;
80 if (!container) return;
81
82 const iframe = container.querySelector('iframe');
83 if (!iframe) return;
84
85 const containerWidth = container.offsetWidth;
86 const scale = Math.min(1, containerWidth / 300); // 300px is approximate base width
87
88 iframe.style.transform = `scale(${scale})`;
89 iframe.style.transformOrigin = 'center center';
90 }
91</script>
92
93{#if !isVerified}
94 <div class="turnstile-wrapper" transition:fade={{ duration: 300 }}>
95 <div bind:this={turnstileElement} class="turnstile-container" data-theme="light"></div>
96 </div>
97{/if}
98
99<style>
100 .turnstile-wrapper {
101 width: 100%;
102 display: flex;
103 justify-content: center;
104 align-items: center;
105 min-height: 65px; /* Approximate height for compact size */
106 overflow: hidden;
107 }
108
109 .turnstile-container {
110 display: flex;
111 justify-content: center;
112 align-items: center;
113 }
114
115 /* Handle iframe scaling */
116 :global(.turnstile-wrapper iframe) {
117 transition: transform 0.2s ease-in-out;
118 }
119</style>