Skip to content

Commit

Permalink
feat: @svelte-put/cloudflare-turnstile first dev version
Browse files Browse the repository at this point in the history
  • Loading branch information
vnphanquang committed Feb 2, 2024
1 parent b988b59 commit 35196bf
Show file tree
Hide file tree
Showing 18 changed files with 525 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-numbers-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@svelte-put/cloudflare-turnstile': minor
---

first release, extracted from sveltevietnam.dev
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Useful svelte stuff to put in your projects
| ----------------------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------- |
| [@svelte-put/avatar][github.avatar] | component & utilities for avatar | [![npm.avatar.badge]][npm.avatar] | [Changelog][github.avatar.changelog] | [![docs.badge]][docs.avatar] |
| [@svelte-put/clickoutside][github.clickoutside] | event for clicking outside node | [![npm.clickoutside.badge]][npm.clickoutside] | [Changelog][github.clickoutside.changelog] | [![docs.badge]][docs.clickoutside] |
| [@svelte-put/cloudflare-turnstile][github.cloudflare-turnstile] | event for clicking outside node | [![npm.cloudflare-turnstile.badge]][npm.cloudflare-turnstile] | [Changelog][github.cloudflare-turnstile.changelog] | [![docs.badge]][docs.cloudflare-turnstile] |
| [@svelte-put/copy][github.copy] | copy text to clipboard | [![npm.copy.badge]][npm.copy] | [Changelog][github.copy.changelog] | [![docs.badge]][docs.copy] |
| [@svelte-put/dragscroll][github.dragscroll] | add drag-to-scroll behavior | [![npm.dragscroll.badge]][npm.dragscroll] | [Changelog][github.dragscroll.changelog] | [![docs.badge]][docs.dragscroll] |
| [@svelte-put/intersect][github.intersect] | wrapper for IntersectionObserver | [![npm.intersect.badge]][npm.intersect] | [Changelog][github.intersect.changelog] | [![docs.badge]][docs.intersect] |
Expand Down Expand Up @@ -68,6 +69,8 @@ pnpm turbo
[github.avatar.changelog]: https://github.com/vnphanquang/svelte-put/blob/main/packages/misc/avatar/CHANGELOG.md
[github.clickoutside]: https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/clickoutside
[github.clickoutside.changelog]: https://github.com/vnphanquang/svelte-put/blob/main/packages/actions/clickoutside/CHANGELOG.md
[github.cloudflare-turnstile]: https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/cloudflare-turnstile
[github.cloudflare-turnstile.changelog]: https://github.com/vnphanquang/svelte-put/blob/main/packages/actions/cloudflare-turnstile/CHANGELOG.md
[github.copy]: https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/copy
[github.copy.changelog]: https://github.com/vnphanquang/svelte-put/blob/main/packages/actions/copy/CHANGELOG.md
[github.dragscroll]: https://github.com/vnphanquang/svelte-put/tree/main/packages/actions/dragscroll
Expand Down Expand Up @@ -114,6 +117,8 @@ pnpm turbo
[npm.avatar]: https://www.npmjs.com/package/@svelte-put/avatar
[npm.clickoutside.badge]: https://img.shields.io/npm/v/@svelte-put/clickoutside
[npm.clickoutside]: https://www.npmjs.com/package/@svelte-put/clickoutside
[npm.cloudflare-turnstile.badge]: https://img.shields.io/npm/v/@svelte-put/cloudflare-turnstile
[npm.cloudflare-turnstile]: https://www.npmjs.com/package/@svelte-put/cloudflare-turnstile
[npm.copy.badge]: https://img.shields.io/npm/v/@svelte-put/copy
[npm.copy]: https://www.npmjs.com/package/@svelte-put/copy
[npm.dragscroll.badge]: https://img.shields.io/npm/v/@svelte-put/dragscroll
Expand Down Expand Up @@ -154,6 +159,7 @@ pnpm turbo
[docs]: https://svelte-put.vnphanquang.com
[docs.avatar]: https://svelte-put.vnphanquang.com/docs/avatar
[docs.clickoutside]: https://svelte-put.vnphanquang.com/docs/clickoutside
[docs.cloudflare-turnstile]: https://svelte-put.vnphanquang.com/docs/cloudflare-turnstile
[docs.copy]: https://svelte-put.vnphanquang.com/docs/copy
[docs.dragscroll]: https://svelte-put.vnphanquang.com/docs/dragscroll
[docs.intersect]: https://svelte-put.vnphanquang.com/docs/intersect
Expand Down
1 change: 1 addition & 0 deletions packages/actions/cloudflare-turnstile/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
52 changes: 52 additions & 0 deletions packages/actions/cloudflare-turnstile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div align="center">

# `@svelte-put/cloudflare-turnstile`

[![npm.badge]][npm] [![bundlephobia.badge]][bundlephobia] [![docs.badge]][docs] [![repl.badge]][repl]

Svelte action `use:cloudflare-turnstile` - event for clicking outside a node

</div>

## `svelte-put`

This package is part of the [@svelte-put][github.monorepo] family. For contributing guideline and more, refer to its [readme][github.monorepo].

## Usage & Documentation

[See the dedicated documentation page here][docs].

## Quick Start

```html
<!-- component.svelte -->
<script lang="ts">
import { turnstile } from '@svelte-put/cloudflare-turnstile';
</script>

<div
use:turnstile
turnstile-sitekey="1x00000000000000000000AA"
turnstile-theme="dark"
turnstile-size="normal"
/>
```

## [Changelog][github.changelog]

<!-- github specifics -->

[github.monorepo]: https://github.com/vnphanquang/svelte-put
[github.changelog]: https://github.com/vnphanquang/svelte-put/blob/main/packages/actions/cloudflare-turnstile/CHANGELOG.md
[github.issues]: https://github.com/vnphanquang/svelte-put/issues?q=

<!-- heading badge -->

[npm.badge]: https://img.shields.io/npm/v/@svelte-put/cloudflare-turnstile
[npm]: https://www.npmjs.com/package/@svelte-put/cloudflare-turnstile
[bundlephobia.badge]: https://img.shields.io/bundlephobia/minzip/@svelte-put/cloudflare-turnstile?label=minzipped
[bundlephobia]: https://bundlephobia.com/package/@svelte-put/cloudflare-turnstile
[repl]: https://svelte.dev/repl/9e5f9ee41c2c45aa8523993e357f6e78
[repl.badge]: https://img.shields.io/static/v1?label=&message=Svelte+REPL&logo=svelte&logoColor=fff&color=ff3e00
[docs]: https://svelte-put.vnphanquang.com/docs/cloudflare-turnstile
[docs.badge]: https://img.shields.io/badge/-Docs%20Site-blue
49 changes: 49 additions & 0 deletions packages/actions/cloudflare-turnstile/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@svelte-put/cloudflare-turnstile",
"version": "0.0.0",
"description": "Action for rendering Cloudflare turnstile into HTML node",
"main": "src/index.js",
"module": "src/index.js",
"types": "types/index.d.ts",
"type": "module",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./src/index.js"
}
},
"publishConfig": {
"access": "public"
},
"files": [
"src",
"types"
],
"scripts": {
"lint": "eslint --ignore-path .gitignore \"./**/*/*{ts,js,cjs}\"",
"format": "prettier --check --ignore-path .gitignore \"./**/*.{ts,js,cjs}\"",
"dts": "dts-buddy && prettier types/index.d.ts --write && publint",
"prepublishOnly": "pnpm dts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vnphanquang/svelte-put.git"
},
"keywords": [
"svelte",
"action",
"turnstile",
"cloudflare",
"captcha"
],
"author": "Quang Phan",
"license": "MIT",
"bugs": {
"url": "https://github.com/vnphanquang/svelte-put/issues"
},
"homepage": "https://svelte-put.vnphanquang.com/docs/cloudflare-turnstile",
"devDependencies": {
"@svelte-put/tsconfig": "workspace:*",
"dts-buddy": "^0.4.0"
}
}
9 changes: 9 additions & 0 deletions packages/actions/cloudflare-turnstile/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Quang Phan. All rights reserved. Licensed under the MIT license.

/**
* `use:turnstile` - svelte action that renders cloudflare turnstile into an HTML node.
*
* @packageDocumentation
*/

export * from './turnstile.js';
105 changes: 105 additions & 0 deletions packages/actions/cloudflare-turnstile/src/public.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ActionReturn, Action } from 'svelte/action';

export type Turnstile = {
render: (element: string | HTMLElement, config: TurnstileConfig) => string;
reset: (widgetId: string) => void;
remove: (widgetId: string) => void;
getResponse: (widgetId: string) => string | undefined;
isExpired: (widgetId: string) => boolean;
execute: (container: string | HTMLElement, config?: TurnstileConfig) => void;
};

declare global {
interface Window {
turnstile?: Turnstile;
}
}

export type TurnstileConfig = TurnstileDataConfig & TurnstileEventConfig;

/**
* @internal
*/
export type TurnstileDataConfig = {
sitekey: string;
action?: string;
cData?: string;
execution?: string;
theme?: 'light' | 'dark' | 'auto';
language?: string;
tabindex?: number;
'response-field'?: boolean;
'response-field-name'?: string;
size?: 'normal' | 'compact';
retry?: 'auto' | 'never';
'retry-interval'?: number;
'refresh-expired'?: 'auto' | 'manual' | 'never' | 'auto';
appearance?: 'always' | 'execute' | 'interaction-only';
};

/**
* @internal
*/
export type TurnstileEventConfig = {
callback?: (token: string) => void;
'error-callback'?: (code: string) => void;
'expired-callback'?: () => void;
'before-interactive-callback'?: () => void;
'after-interactive-callback'?: () => void;
'unsupported-callback'?: () => void;
'timeout-callback'?: () => void;
};

/**
* @internal
*/
export type TurnstileDataAttributes = {
[K in keyof TurnstileDataConfig as K extends string
? `turnstile-${K}`
: never]: TurnstileDataConfig[K];
};

export type TurnstileEventDetail<T extends Record<string, any> = Record<string, never>> = {
widgetId: string;
turnstile: Turnstile;
} & T;

/**
* @internal
*/
export type TurnstileEventAttributes = {
'on:turnstile'?: (event: CustomEvent<TurnstileEventDetail<{ token: string }>>) => void;
'on:turnstile:error'?: (event: CustomEvent<TurnstileEventDetail<{ code: string }>>) => void;
'on:turnstile:expired'?: (event: CustomEvent<TurnstileEventDetail>) => void;
'on:turnstile:before-interactive'?: (event: CustomEvent<TurnstileEventDetail>) => void;
'on:turnstile:after-interactive'?: (event: CustomEvent<TurnstileEventDetail>) => void;
'on:turnstile:unsupported'?: (event: CustomEvent<TurnstileEventDetail>) => void;
'on:turnstile:timeout'?: (event: CustomEvent<TurnstileEventDetail>) => void;
};

/**
* @see {@link https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#configurations | Cloudflare Turnstile Docs}
* @public
*/
export type TurnstileAttributes = TurnstileDataAttributes &
TurnstileEventAttributes & {
/**
* default to `https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit`
*/
'turnstile-script-src'?: string;
readonly 'turnstile-widget-id'?: string;
readonly 'turnstile-rendered'?: boolean;
};

/**
* parameter received from action input
* @public
*/
export type TurnstileParameter = undefined;

/** @public */
export type TurnstileAction = Action<HTMLElement, TurnstileParameter, TurnstileAttributes>;

/** @public */
export type TurnstileActionReturn = ActionReturn<TurnstileParameter, TurnstileAttributes>;
117 changes: 117 additions & 0 deletions packages/actions/cloudflare-turnstile/src/turnstile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* @param {HTMLElement} node
* @returns {import('./public').TurnstileActionReturn}
*/
export function turnstile(node) {
/** @type {string | undefined} */
let widgetId = undefined;

if (!window.turnstile) {
const script = document.createElement('script');
const src =
node.getAttribute('turnstile-script-src') ??
'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';

script.setAttribute('src', src);
document.head.appendChild(script);
script.addEventListener('load', load);
} else if (!widgetId) {
load();
}

/**
* @template {keyof import('./public').TurnstileEventAttributes extends `on:${infer K}` ? K : never} E
* @template [D=Parameters<NonNullable<import('./public').TurnstileEventAttributes[`on:${E}`]>>[0]['detail'] extends import('./public').TurnstileEventDetail<infer T extends Record<string, any>> ? T : never]
* @param {E} event
* @param {D} detail
*/
function dispatchEvent(event, detail) {
/** @type {D} */
detail = {
widgetId,
turnstile: window.turnstile,
...detail,
};
node.dispatchEvent(new CustomEvent(event, { detail }));
}

function load() {
const sitekey = node.getAttribute('turnstile-sitekey');
if (!sitekey) throw new Error('Attribute `turnstile-sitekey` is required but not provided');

/** @type {import('./public').TurnstileConfig} */
const config = {
// data
sitekey,
action: node.getAttribute('turnstile-action') ?? undefined,
cData: node.getAttribute('turnstile-cData') ?? undefined,
execution: node.getAttribute('turnstile-execution') ?? undefined,
theme:
/** @type {import('./public').TurnstileConfig['theme']} */ (
node.getAttribute('turnstile-theme')
) ?? undefined,
language: node.getAttribute('turnstile-language') ?? undefined,
tabindex: parseInt(node.getAttribute('turnstile-tabindex') ?? '0'),
'response-field': node.hasAttribute('turnstile-response-field'),
'response-field-name': node.getAttribute('turnstile-response-field-name') ?? undefined,
size:
/** @type {import('./public').TurnstileConfig['size']} */ (
node.getAttribute('turnstile-size')
) ?? undefined,
retry:
/** @type {import('./public').TurnstileConfig['retry']} */ (
node.getAttribute('turnstile-retry')
) ?? undefined,
'retry-interval': parseInt(node.getAttribute('turnstile-retry-interval') ?? '0'),
'refresh-expired':
/** @type {import('./public').TurnstileConfig['refresh-expired']} */ (
node.getAttribute('turnstile-refresh-expired')
) ?? undefined,
appearance:
/** @type {import('./public').TurnstileConfig['appearance']} */ (
node.getAttribute('turnstile-appearance')
) ?? undefined,

// events
callback: (token) => {
dispatchEvent('turnstile', { token });
node.toggleAttribute('turnstile-rendered', true);
},
'error-callback': (code) => {
dispatchEvent('turnstile:error', { code });
},
'expired-callback': (...params) => {
// TODO: requires testing
dispatchEvent('turnstile:expired', { ...(params.length ? { params } : {}) });
},
'before-interactive-callback': () => {
dispatchEvent('turnstile:before-interactive', {});
},
'after-interactive-callback': () => {
dispatchEvent('turnstile:after-interactive', {});
},
'unsupported-callback': (...params) => {
// TODO: requires testing
dispatchEvent('turnstile:unsupported', { ...(params.length ? { params } : {}) });
},
'timeout-callback': (...params) => {
// TODO: requires testing
dispatchEvent('turnstile:timeout', { ...(params.length ? { params } : {}) });
},
};

widgetId = window.turnstile?.render(node, config);
if (widgetId) {
node.setAttribute('turnstile-widget-id', widgetId);
}
}

return {
destroy() {
if (widgetId) {
window.turnstile?.remove(widgetId);
widgetId = undefined;
}
},
};
}
4 changes: 4 additions & 0 deletions packages/actions/cloudflare-turnstile/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"include": ["src/**/*"],
"extends": "@svelte-put/tsconfig/base.package.js.json"
}
Loading

0 comments on commit 35196bf

Please sign in to comment.