Skip to content

Upgrade to Svelte 5 #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "3.3.1",
"@sveltejs/kit": "2.7.3",
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/kit": "^2.17.1",
"@sveltejs/package": "^2.3.7",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"ghostsui": "^1.6.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"publint": "^0.2.12",
"sass": "^1.83.1",
"svelte": "^4.2.19",
"svelte": "^5.20.0",
"svelte-check": "^4.1.1",
"sveltekit-superforms": "^2.22.1",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vite": "^6.1.0",
"zod": "^3.24.1"
},
"peerDependencies": {
Expand Down
254 changes: 57 additions & 197 deletions src/lib/Turnstile.svelte
Original file line number Diff line number Diff line change
@@ -1,193 +1,73 @@
<script lang="ts" module>
let loadPromise: Promise<void> | undefined;

function addScript(): Promise<void> {
if (!(window && 'document' in window)) {
return new Promise(() => {});
}
if (loadPromise === undefined) {
loadPromise = new Promise((resolve) => {
const script = document.createElement('script');
script.src =
'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
script.async = true;
script.addEventListener('load', () => resolve(), {
once: true,
});
document.head.appendChild(script);
});
}
return loadPromise;
}
</script>

<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import type { Action } from 'svelte/action';
import type { Events } from './types';
import type {
RenderParameters,
TurnstileObject,
WidgetId,
} from 'turnstile-types';

const dispatch = createEventDispatcher<Events>();

let loaded = typeof window != 'undefined' && 'turnstile' in window;
let mounted = false;

/**
* Represents a rendered Turnstile widget. Used to identify a specific widget when calling
* Turnstile methods.
*/
export let widgetId: WidgetId | null = null;

/**
* Turnstile is Cloudflare’s smart CAPTCHA alternative. It can be embedded into any website
* without sending traffic through Cloudflare and works without showing visitors a CAPTCHA.
* @see https://developers.cloudflare.com/turnstile
*/
export let turnstile: TurnstileObject | null = null;
$: turnstile = (loaded && window.turnstile) || null;

/**
* Every widget has a sitekey. This sitekey is associated with the corresponding
* widget configuration and is created upon the widget creation.
*/
export let siteKey: RenderParameters['sitekey'];

/**
* Controls when the widget is visible:
* - `"always"` - The widget is visible at all times.
* - `"execute"` - The widget is visible only after the challenge begins.
* - `"interaction-only"` - The widget is visible only when an interaction is required.
*
* If a widget is visible, its appearance can be controlled via the `appearance` parameter.
* @see
* [appearance-modes](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#appearance-modes)
*/
export let appearance: RenderParameters['appearance'] = 'always';

/**
* Language to display, either `"auto"` or an ISO 639-1 two-letter language code.
* @see [language support FAQ](https://developers.cloudflare.com/turnstile/frequently-asked-questions/#what-languages-does-turnstile-support)
*/
export let language: RenderParameters['language'] = 'auto' as const;

/**
* Execution controls when to obtain the token of the widget and can be on `"render"` (default) or on `"execute"`.
* @default "render"
* @see [Execution modes](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#execution-modes)
*/
export let execution: RenderParameters['execution'] = 'render';

/**
* A customer value that can be used to differentiate widgets under the same
* sitekey in analytics and which is returned upon validation. This can only
* contain up to 32 alphanumeric characters including `_` and `-`.
*/
export let action: RenderParameters['action'] = undefined;

/**
* A customer payload that can be used to attach customer data to the challenge
* throughout its issuance and which is returned upon validation. This can only
* contain up to 255 alphanumeric characters including `_` and `-`.
*/
export let cData: RenderParameters['cData'] = undefined;

/**
* Time between retry attempts in milliseconds. Value must be between `0` and `900000`
* (15 minutes). Only applies when `retry` is set to `auto`.
* @default 8000
*/
export let retryInterval: RenderParameters['retry-interval'] = 8000;

/**
* Automatically retry upon failure to obtain a token or never retry.
* @default "auto"
*/
export let retry: RenderParameters['retry'] = 'auto';

/**
* Controls the behavior when the token of a Turnstile widget has expired.
* Can be 'auto', 'manual', or 'never'.
* @default "auto"
*/
export let refreshExpired: RenderParameters['refresh-expired'] = 'auto';

/**
* The widget theme. Can be `"light"`, `"dark"`, or `"auto"`.
*/
export let theme: RenderParameters['theme'] = 'auto';

/**
* The widget size. Can be 'normal', 'flexible', 'invisible', or 'compact'.
* @default "normal"
*/
export let size: RenderParameters['size'] = 'normal';

/**
* The tabindex of Turnstile's iframe for accessibility purposes.
* @default 0
*/
export let tabIndex = 0;

/**
* @deprecated Use `responseField` instead.
*/
export let forms: RenderParameters['response-field'] = undefined;

/**
* Controls if an input element with the response token is created.
* @default true
*/
export let responseField: RenderParameters['response-field'] = undefined;

/**
* @deprecated Use `responseFieldName` instead.
*/
export let formsField: RenderParameters['response-field-name'] = undefined;

/**
* Name of the input element.
* @default "cf-turnstile-response"
*/
export let responseFieldName: RenderParameters['response-field-name'] =
undefined;

/**
* Classes to apply to the wrapper div around turnstile.
* This won't work with Svelte scoped styles.
*/
let _class: string | undefined = undefined;
export { _class as class };
let {
widgetId = $bindable(null),
turnstile = $bindable(null),
class: _class,
...renderParams
}: {
/**
* Represents a rendered Turnstile widget. Used to identify a specific widget when calling
* Turnstile methods.
*/
widgetId?: WidgetId | null;

/**
* Turnstile is Cloudflare’s smart CAPTCHA alternative. It can be embedded into any website
* without sending traffic through Cloudflare and works without showing visitors a CAPTCHA.
* @see https://developers.cloudflare.com/turnstile
*/
turnstile?: TurnstileObject | null;

/**
* Classes to apply to the wrapper div around turnstile.
* This won't work with Svelte scoped styles.
*/
class?: string;
} & RenderParameters = $props();

const loadScript = addScript();

loadScript.then(() => {
turnstile = window.turnstile || null;
});

/**
* Resets the widget.
* @param widgetId - The ID of the widget.
*/
export const reset: TurnstileObject['reset'] = (): void => {
widgetId && window?.turnstile?.reset(widgetId);
};

$: renderParams = {
sitekey: siteKey,
callback: (token: string, preClearanceObtained: boolean) => {
dispatch('callback', { token, preClearanceObtained });
dispatch('turnstile-callback', { token, preClearanceObtained });
},
'error-callback': (code) => {
dispatch('error', { code });
dispatch('turnstile-error', { code });
},
'timeout-callback': () => {
dispatch('timeout', {});
dispatch('turnstile-timeout', {});
},
'expired-callback': () => {
dispatch('expired', {});
dispatch('turnstile-expired', {});
},
'before-interactive-callback': () => {
dispatch('before-interactive', {});
},
'after-interactive-callback': () => {
dispatch('after-interactive', {});
},
'unsupported-callback': () => dispatch('unsupported', {}),
'response-field-name':
responseFieldName ?? formsField ?? 'cf-turnstile-response',
'response-field': responseField ?? forms ?? true,
'refresh-expired': refreshExpired,
'retry-interval': retryInterval,
tabindex: tabIndex,
appearance,
execution,
language,
action,
retry,
theme,
cData,
size,
} satisfies RenderParameters;

const turnstileAction: Action<HTMLElement, RenderParameters> = (
node: HTMLElement,
renderParams: RenderParameters,
Expand All @@ -206,35 +86,15 @@
},
};
};

onMount(() => {
mounted = true;

if (!loaded) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src =
'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
script.async = true;
script.addEventListener('load', () => (loaded = true), {
once: true,
});
document.head.appendChild(script);
}

return () => {
mounted = false;
};
});
</script>

{#if loaded && mounted}
{#await loadScript then}
<div
use:turnstileAction={renderParams}
class:flexible={size == 'flexible'}
class:flexible={renderParams.size == 'flexible'}
class={_class}>
</div>
{/if}
{/await}

<style>
:where(.flexible) {
Expand Down
10 changes: 4 additions & 6 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
export type * from './types.d';

// Export Turnstile component
export { default as Turnstile } from './Turnstile.svelte';

import type { TurnstileOptions } from 'turnstile-types';
import type { RenderParameters } from 'turnstile-types';

// Backwards compatibility (semver) for when we switched to turnstile-types
export type TurnstileTheme = TurnstileOptions['theme'];
export type TurnstileLanguage = TurnstileOptions['language'];
export type TurnstileSize = TurnstileOptions['size'];
export type TurnstileTheme = RenderParameters['theme'];
export type TurnstileLanguage = RenderParameters['language'];
export type TurnstileSize = RenderParameters['size'];
65 changes: 0 additions & 65 deletions src/lib/types.d.ts

This file was deleted.

Loading