react-turnstile
Advanced tools
Comparing version 1.1.0 to 1.1.1
import React from "react"; | ||
import { SupportedLanguages } from "turnstile-types"; | ||
export default function Turnstile({ id, className, style, sitekey, action, cData, theme, language, tabIndex, responseField, responseFieldName, size, retry, retryInterval, refreshExpired, appearance, execution, userRef, onVerify, onLoad, onError, onExpire, onTimeout, }: TurnstileProps): JSX.Element; | ||
interface TurnstileProps extends TurnstileCallbacks { | ||
import { TurnstileObject, TurnstileOptions, SupportedLanguages } from "turnstile-types"; | ||
export default function Turnstile({ id, className, style, sitekey, action, cData, theme, language, tabIndex, responseField, responseFieldName, size, fixedSize, retry, retryInterval, refreshExpired, appearance, execution, userRef, onVerify, onLoad, onError, onExpire, onTimeout, }: TurnstileProps): JSX.Element; | ||
export interface TurnstileProps extends TurnstileCallbacks { | ||
sitekey: string; | ||
@@ -14,2 +14,3 @@ action?: string; | ||
size?: "normal" | "invisible" | "compact"; | ||
fixedSize?: boolean; | ||
retry?: "auto" | "never"; | ||
@@ -25,9 +26,14 @@ retryInterval?: number; | ||
} | ||
interface TurnstileCallbacks { | ||
onVerify: (token: string) => void; | ||
onLoad?: (widgetId: string) => void; | ||
onError?: (error?: Error | any) => void; | ||
onExpire?: () => void; | ||
onTimeout?: () => void; | ||
export interface TurnstileCallbacks { | ||
onVerify: (token: string, boundTurnstile: BoundTurnstileObject) => void; | ||
onLoad?: (widgetId: string, boundTurnstile: BoundTurnstileObject) => void; | ||
onError?: (error?: Error | any, boundTurnstile?: BoundTurnstileObject) => void; | ||
onExpire?: (token: string, boundTurnstile: BoundTurnstileObject) => void; | ||
onTimeout?: (boundTurnstile: BoundTurnstileObject) => void; | ||
} | ||
export {}; | ||
export interface BoundTurnstileObject { | ||
execute: (options?: TurnstileOptions) => void; | ||
reset: () => void; | ||
getResponse: () => void; | ||
} | ||
export declare function useTurnstile(): TurnstileObject; |
@@ -26,24 +26,25 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.useTurnstile = void 0; | ||
const react_1 = __importStar(require("react")); | ||
const global = (typeof globalThis !== "undefined" ? globalThis : window); | ||
let turnstileState = typeof global.turnstile !== "undefined" ? "ready" : "unloaded"; | ||
const globalNamespace = (typeof globalThis !== "undefined" ? globalThis : window); | ||
let turnstileState = typeof globalNamespace.turnstile !== "undefined" ? "ready" : "unloaded"; | ||
let ensureTurnstile; | ||
// Functions responsible for loading the turnstile api, while also making sure | ||
// to only load it once | ||
let turnstileLoad; | ||
const turnstileLoadPromise = new Promise((resolve, reject) => { | ||
turnstileLoad = { resolve, reject }; | ||
if (turnstileState === "ready") | ||
resolve(undefined); | ||
}); | ||
{ | ||
const TURNSTILE_LOAD_FUNCTION = "cf__reactTurnstileOnLoad"; | ||
const TURNSTILE_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js"; | ||
let turnstileLoad; | ||
const turnstileLoadPromise = new Promise((resolve, reject) => { | ||
turnstileLoad = { resolve, reject }; | ||
if (turnstileState === "ready") | ||
resolve(undefined); | ||
}); | ||
ensureTurnstile = () => { | ||
if (turnstileState === "unloaded") { | ||
turnstileState = "loading"; | ||
global[TURNSTILE_LOAD_FUNCTION] = () => { | ||
globalNamespace[TURNSTILE_LOAD_FUNCTION] = () => { | ||
turnstileLoad.resolve(); | ||
turnstileState = "ready"; | ||
delete global[TURNSTILE_LOAD_FUNCTION]; | ||
delete globalNamespace[TURNSTILE_LOAD_FUNCTION]; | ||
}; | ||
@@ -56,3 +57,3 @@ const url = `${TURNSTILE_SRC}?onload=${TURNSTILE_LOAD_FUNCTION}&render=explicit`; | ||
turnstileLoad.reject("Failed to load Turnstile."); | ||
delete global[TURNSTILE_LOAD_FUNCTION]; | ||
delete globalNamespace[TURNSTILE_LOAD_FUNCTION]; | ||
}); | ||
@@ -64,3 +65,3 @@ document.head.appendChild(script); | ||
} | ||
function Turnstile({ id, className, style, sitekey, action, cData, theme, language, tabIndex, responseField, responseFieldName, size, retry, retryInterval, refreshExpired, appearance, execution, userRef, onVerify, onLoad, onError, onExpire, onTimeout, }) { | ||
function Turnstile({ id, className, style, sitekey, action, cData, theme, language, tabIndex, responseField, responseFieldName, size, fixedSize, retry, retryInterval, refreshExpired, appearance, execution, userRef, onVerify, onLoad, onError, onExpire, onTimeout, }) { | ||
const ownRef = (0, react_1.useRef)(null); | ||
@@ -73,3 +74,3 @@ const inplaceState = (0, react_1.useState)({ onVerify })[0]; | ||
let cancelled = false; | ||
let widgetId = ""; | ||
let widgetId = "", timeoutId = 0; | ||
(async () => { | ||
@@ -89,2 +90,3 @@ var _a, _b; | ||
return; | ||
let boundTurnstileObject; | ||
const turnstileOptions = { | ||
@@ -100,3 +102,3 @@ sitekey, | ||
size, | ||
retry, | ||
retry: "never", | ||
"retry-interval": retryInterval, | ||
@@ -106,9 +108,29 @@ "refresh-expired": refreshExpired, | ||
execution, | ||
callback: (token) => inplaceState.onVerify(token), | ||
"error-callback": () => { var _a; return (_a = inplaceState.onError) === null || _a === void 0 ? void 0 : _a.call(inplaceState); }, | ||
"expired-callback": () => { var _a; return (_a = inplaceState.onExpire) === null || _a === void 0 ? void 0 : _a.call(inplaceState); }, | ||
"timeout-callback": () => { var _a; return (_a = inplaceState.onTimeout) === null || _a === void 0 ? void 0 : _a.call(inplaceState); }, | ||
callback: (token) => inplaceState.onVerify(token, boundTurnstileObject), | ||
"error-callback": (error) => { | ||
var _a; | ||
// we handle retry ourselves because turnstile does not properly | ||
// reset its timeout when calling turnstile.remove, logging the | ||
// following in the console: | ||
// > [Cloudflare Turnstile] Nothing to reset found for provided container. | ||
// refs: | ||
// - https://github.com/Le0Developer/react-turnstile/issues/14 | ||
// - https://discord.com/channels/595317990191398933/1025131875397812224/1122137855368646717 | ||
// TODO: remove when fixed | ||
if (!retry || retry === "auto") { | ||
timeoutId = setTimeout(() => { | ||
boundTurnstileObject.reset(); | ||
timeoutId = 0; | ||
// no need to do bounds checks, turnstile already does them for us | ||
// even though we have retry=never | ||
}, 2000 + (retryInterval !== null && retryInterval !== void 0 ? retryInterval : 8000)); | ||
} | ||
(_a = inplaceState.onError) === null || _a === void 0 ? void 0 : _a.call(inplaceState, error, boundTurnstileObject); | ||
}, | ||
"expired-callback": (token) => { var _a; return (_a = inplaceState.onExpire) === null || _a === void 0 ? void 0 : _a.call(inplaceState, token, boundTurnstileObject); }, | ||
"timeout-callback": () => { var _a; return (_a = inplaceState.onTimeout) === null || _a === void 0 ? void 0 : _a.call(inplaceState, boundTurnstileObject); }, | ||
}; | ||
widgetId = window.turnstile.render(ref.current, turnstileOptions); | ||
(_b = inplaceState.onLoad) === null || _b === void 0 ? void 0 : _b.call(inplaceState, widgetId); | ||
boundTurnstileObject = createBoundTurnstileObject(widgetId); | ||
(_b = inplaceState.onLoad) === null || _b === void 0 ? void 0 : _b.call(inplaceState, widgetId, boundTurnstileObject); | ||
})(); | ||
@@ -119,2 +141,4 @@ return () => { | ||
window.turnstile.remove(widgetId); | ||
if (timeoutId) | ||
clearTimeout(timeoutId); | ||
}; | ||
@@ -144,5 +168,30 @@ }, [ | ||
}, [onVerify, onLoad, onError, onExpire, onTimeout]); | ||
return react_1.default.createElement("div", { ref: ref, id: id, className: className, style: style }); | ||
return (react_1.default.createElement("div", { ref: ref, id: id, className: className, style: fixedSize | ||
? { | ||
...(style !== null && style !== void 0 ? style : {}), | ||
width: size === "compact" ? "130px" : "300px", | ||
height: size === "compact" ? "120px" : "65px", | ||
} | ||
: style })); | ||
} | ||
exports.default = Turnstile; | ||
function createBoundTurnstileObject(widgetId) { | ||
return { | ||
execute: (options) => window.turnstile.execute(widgetId, options), | ||
reset: () => window.turnstile.reset(widgetId), | ||
getResponse: () => window.turnstile.getResponse(widgetId), | ||
}; | ||
} | ||
function useTurnstile() { | ||
// we are using state here to trigger a component re-render once turnstile | ||
// loads, so the component using this hook gets the object once its loaded | ||
const [_, setState] = (0, react_1.useState)(turnstileState); | ||
(0, react_1.useEffect)(() => { | ||
if (turnstileState === "ready") | ||
return; | ||
turnstileLoadPromise.then(() => setState(turnstileState)); | ||
}, []); | ||
return globalNamespace.turnstile; | ||
} | ||
exports.useTurnstile = useTurnstile; | ||
//# sourceMappingURL=index.js.map |
@@ -10,2 +10,17 @@ # Changelog | ||
## [1.1.1] - 2023-06-25 | ||
### Added | ||
- `useTurnstile` hook | ||
- `fixedSize` option to reduce layout shift | ||
- Missing argument for `onExpire` | ||
- `BoundTurnstileObject` argument to callbacks | ||
### Fixed | ||
- `global` -> `globalNamespace` (name conflict) (#10) | ||
- Implement `retry` logic ourselves (#14) | ||
- Missing `onError` error argument passthrough | ||
## [1.1.0] - 2023-03-09 | ||
@@ -91,3 +106,4 @@ | ||
[unreleased]: https://github.com/Le0Developer/react-turnstile/compare/v1.1.0...HEAD | ||
[unreleased]: https://github.com/Le0Developer/react-turnstile/compare/v1.1.1...HEAD | ||
[1.1.1]: https://github.com/le0developer/react-turnstile/compare/v1.1.0...v1.1.1 | ||
[1.1.0]: https://github.com/le0developer/react-turnstile/compare/v1.0.6...v1.1.0 | ||
@@ -94,0 +110,0 @@ [1.0.6]: https://github.com/le0developer/react-turnstile/compare/v1.0.5...v1.0.6 |
{ | ||
"name": "react-turnstile", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"description": "React library for Cloudflare's Turnstile CAPTCHA alternative", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -14,3 +14,3 @@ # react-turnstile | ||
```jsx | ||
import Turnstile from "react-turnstile"; | ||
import Turnstile, { useTurnstile } from "react-turnstile"; | ||
@@ -20,6 +20,14 @@ // ... | ||
function TurnstileWidget() { | ||
const turnstile = useTurnstile(); | ||
return ( | ||
<Turnstile | ||
sitekey="1x00000000000000000000AA" | ||
onVerify={(token) => alert(token)} | ||
onVerify={(token) => { | ||
fetch("/login", { | ||
method: "POST", | ||
body: JSON.stringify({ token }), | ||
}).then((response) => { | ||
if (!response.ok) turnstile.reset(); | ||
}); | ||
}} | ||
/> | ||
@@ -33,2 +41,34 @@ ); | ||
### Reducing Layout Shift | ||
The turnstile iframe initially loads as invisible before becoming visible and | ||
expanding to the expected widget size. | ||
This causes Layout Shift and reduces your Cumulative Layout Shift score and UX. | ||
This can be fixed with the `fixedSize={true}` option, which will force the | ||
wrapper div to be the specific size of turnstile. | ||
### Bound Turnstile Object | ||
The Bound Turnstile Object is given as argument to all callbacks and allows you | ||
to call certain `window.turnstile` functions without having to store the `widgetId` | ||
yourself. | ||
```js | ||
function Component() { | ||
return ( | ||
<Turnstile | ||
executution="execute" | ||
onLoad={(widgetId, bound) => { | ||
// before: | ||
window.turnstile.execute(widgetId); | ||
// now: | ||
bound.execute(); | ||
}} | ||
/> | ||
); | ||
} | ||
``` | ||
## Documentation | ||
@@ -38,18 +78,22 @@ | ||
| name | type | description | | ||
| ----------------- | ------- | ----------------------------------------------------- | | ||
| sitekey | string | sitekey of your website (REQUIRED) | | ||
| action | string | - | | ||
| cData | string | - | | ||
| theme | string | one of "light", "dark", "auto" | | ||
| tabIndex | number | - | | ||
| responseField | boolean | controls generation of `<input />` element | | ||
| responseFieldName | string | changes the name of `<input />` element | | ||
| retry | string | one of "auto", "never" | | ||
| retryInterval | number | interval of retries in ms | | ||
| autoResetOnExpire | boolean | automatically reset the widget when the token expires | | ||
| id | string | id of the div | | ||
| ref | Ref | custom react ref for the div | | ||
| className | string | passed to the div | | ||
| style | object | passed to the div | | ||
| name | type | description | | ||
| ----------------- | ------- | ---------------------------------------------------- | | ||
| sitekey | string | sitekey of your website (REQUIRED) | | ||
| action | string | - | | ||
| cData | string | - | | ||
| theme | string | one of "light", "dark", "auto" | | ||
| language | string | override the language used by turnstile | | ||
| tabIndex | number | - | | ||
| responseField | boolean | controls generation of `<input />` element | | ||
| responseFieldName | string | changes the name of `<input />` element | | ||
| size | string | one of "normal", "compact" | | ||
| fixedSize | boolean | fix the size of the `<div />` to reduce layout shift | | ||
| retry | string | one of "auto", "never" | | ||
| retryInterval | number | interval of retries in ms | | ||
| appearance | string | one of "always", "execute", "interaction-only" | | ||
| execution | string | one of "render", "execute" | | ||
| id | string | id of the div | | ||
| ref | Ref | custom react ref for the div | | ||
| className | string | passed to the div | | ||
| style | object | passed to the div | | ||
@@ -64,4 +108,8 @@ And the following callbacks: | ||
| onExpire | - | called when the token expires | | ||
| onTimeout | - | called when the challenge expires | | ||
| onTimeout | token | called when the challenge expires | | ||
The callbacks also take an additional `BoundTurnstileObject` which exposes | ||
certain functions of `window.turnstile` which are already bound to the | ||
current widget, so you don't need track the `widgetId` yourself. | ||
For more details on what each argument does, see the [Cloudflare Documentation](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#configurations). |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
33416
490
111