@rozie-ui/captcha-react 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/index.cjs +4 -5
- package/dist/index.d.cts +37 -0
- package/dist/index.d.mts +37 -0
- package/dist/index.mjs +4 -5
- package/package.json +3 -3
- package/src/Captcha.d.ts +23 -0
- package/src/Captcha.tsx +34 -8
- package/src/RecaptchaV3.d.ts +14 -0
- package/src/RecaptchaV3.tsx +28 -13
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 One Learning Community LTD
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/dist/index.cjs
CHANGED
|
@@ -131,7 +131,6 @@ const Captcha = (0, react.forwardRef)(function Captcha(_props, ref) {
|
|
|
131
131
|
const { provider, sitekey, token, theme, size, tabindex, options, defaultValue, onTokenChange, defaultToken, ...rest } = _props;
|
|
132
132
|
return rest;
|
|
133
133
|
})();
|
|
134
|
-
const disposed = (0, react.useRef)(false);
|
|
135
134
|
const api = (0, react.useRef)(null);
|
|
136
135
|
const widgetId = (0, react.useRef)(null);
|
|
137
136
|
const [token, setToken] = (0, _rozie_runtime_react.useControllableState)({
|
|
@@ -185,9 +184,9 @@ const Captcha = (0, react.forwardRef)(function Captcha(_props, ref) {
|
|
|
185
184
|
return widgetId.current != null && api.current && typeof api.current.getResponse === "function" ? api.current.getResponse(widgetId.current) : "";
|
|
186
185
|
}
|
|
187
186
|
(0, react.useEffect)(() => {
|
|
188
|
-
disposed
|
|
187
|
+
let disposed = false;
|
|
189
188
|
loadCaptchaApi(props.provider).then((a) => {
|
|
190
|
-
if (disposed
|
|
189
|
+
if (disposed) return;
|
|
191
190
|
api.current = a;
|
|
192
191
|
widgetId.current = api.current.render(widgetEl.current, buildConfig());
|
|
193
192
|
}).catch((err) => {
|
|
@@ -197,7 +196,7 @@ const Captcha = (0, react.forwardRef)(function Captcha(_props, ref) {
|
|
|
197
196
|
});
|
|
198
197
|
});
|
|
199
198
|
return () => {
|
|
200
|
-
disposed
|
|
199
|
+
disposed = true;
|
|
201
200
|
if (widgetId.current == null || !api.current) return;
|
|
202
201
|
if (typeof api.current.remove === "function") api.current.remove(widgetId.current);
|
|
203
202
|
else if (typeof api.current.reset === "function") api.current.reset(widgetId.current);
|
|
@@ -315,7 +314,7 @@ const RecaptchaV3 = (0, react.forwardRef)(function RecaptchaV3(_props, ref) {
|
|
|
315
314
|
defaultValue: props.defaultToken ?? "",
|
|
316
315
|
onValueChange: props.onTokenChange
|
|
317
316
|
});
|
|
318
|
-
function execute$1(action
|
|
317
|
+
function execute$1(action) {
|
|
319
318
|
const a = action != null ? action : props.action;
|
|
320
319
|
return loadRecaptchaV3(props.sitekey).then(() => execute(props.sitekey, { action: a })).then((tok) => {
|
|
321
320
|
if (disposed.current) return tok;
|
package/dist/index.d.cts
CHANGED
|
@@ -2,14 +2,37 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/Captcha.d.ts
|
|
4
4
|
interface CaptchaProps {
|
|
5
|
+
/**
|
|
6
|
+
* Which widget to render: `recaptcha` (Google reCAPTCHA v2), `hcaptcha`, `turnstile` (Cloudflare), or `friendly` (Friendly Captcha). The first three share a near-identical explicit-render API; Friendly Captcha rides an internal `adapt()` bridge onto the same surface. Construction-time — re-key the component to switch it live.
|
|
7
|
+
*/
|
|
5
8
|
provider?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Required. The public site key from your provider dashboard. Identifies your site to the chosen provider.
|
|
11
|
+
*/
|
|
6
12
|
sitekey: string;
|
|
13
|
+
/**
|
|
14
|
+
* The verified response token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written by the widget on success and cleared on expire/reset, so reading it gives you the live response to send to your server for form submission.
|
|
15
|
+
* @example
|
|
16
|
+
* <Captcha r-model:token="token" provider="recaptcha" sitekey="…" />
|
|
17
|
+
*/
|
|
7
18
|
token?: string;
|
|
8
19
|
defaultToken?: string;
|
|
9
20
|
onTokenChange?: (next: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Widget color theme: `light` or `dark` (all three core providers), or `auto` (Turnstile only). Construction-time — re-key the component to change it live.
|
|
23
|
+
*/
|
|
10
24
|
theme?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Widget size. reCAPTCHA/hCaptcha accept `normal`/`compact`/`invisible`; Turnstile accepts `normal`/`compact`/`flexible`. A no-op for Friendly Captcha (its `startMode` analog rides through the `options` escape hatch instead). Construction-time.
|
|
27
|
+
*/
|
|
11
28
|
size?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Optional tab index forwarded to the rendered widget. Omitted from the render config when left unset (`null`).
|
|
31
|
+
*/
|
|
12
32
|
tabindex?: (number) | null;
|
|
33
|
+
/**
|
|
34
|
+
* Escape hatch — provider-specific render options merged last (e.g. Turnstile `action`/`cData`/`retry`, hCaptcha `hl`, reCAPTCHA `badge`, Friendly Captcha `startMode`). Lets you reach keys this component does not promote to first-class props.
|
|
35
|
+
*/
|
|
13
36
|
options?: Record<string, unknown>;
|
|
14
37
|
onVerify?: (...args: unknown[]) => void;
|
|
15
38
|
onExpire?: (...args: unknown[]) => void;
|
|
@@ -24,11 +47,25 @@ declare const Captcha: React.ForwardRefExoticComponent<CaptchaProps & React.RefA
|
|
|
24
47
|
//#endregion
|
|
25
48
|
//#region src/RecaptchaV3.d.ts
|
|
26
49
|
interface RecaptchaV3Props {
|
|
50
|
+
/**
|
|
51
|
+
* Required. The public reCAPTCHA v3 site key from your Google admin console.
|
|
52
|
+
*/
|
|
27
53
|
sitekey: string;
|
|
54
|
+
/**
|
|
55
|
+
* The default action label reported to reCAPTCHA's risk analysis (e.g. `submit`, `login`). Overridable per call via `execute(action)`.
|
|
56
|
+
*/
|
|
28
57
|
action?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The latest verification token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written on each successful `execute()` — read it to attach the fresh token to your request.
|
|
60
|
+
* @example
|
|
61
|
+
* <RecaptchaV3 r-model:token="token" sitekey="…" action="signup" />
|
|
62
|
+
*/
|
|
29
63
|
token?: string;
|
|
30
64
|
defaultToken?: string;
|
|
31
65
|
onTokenChange?: (next: string) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Opt in to running one `execute()` at mount and emitting `@verify` with the initial token. Off by default — v3 is imperative-first and tokens are short-lived (~2 min), so fetch one at the moment of submission rather than eagerly at mount.
|
|
68
|
+
*/
|
|
32
69
|
executeOnMount?: boolean;
|
|
33
70
|
onError?: (...args: unknown[]) => void;
|
|
34
71
|
onVerify?: (...args: unknown[]) => void;
|
package/dist/index.d.mts
CHANGED
|
@@ -2,14 +2,37 @@ import * as _$react from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/Captcha.d.ts
|
|
4
4
|
interface CaptchaProps {
|
|
5
|
+
/**
|
|
6
|
+
* Which widget to render: `recaptcha` (Google reCAPTCHA v2), `hcaptcha`, `turnstile` (Cloudflare), or `friendly` (Friendly Captcha). The first three share a near-identical explicit-render API; Friendly Captcha rides an internal `adapt()` bridge onto the same surface. Construction-time — re-key the component to switch it live.
|
|
7
|
+
*/
|
|
5
8
|
provider?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Required. The public site key from your provider dashboard. Identifies your site to the chosen provider.
|
|
11
|
+
*/
|
|
6
12
|
sitekey: string;
|
|
13
|
+
/**
|
|
14
|
+
* The verified response token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written by the widget on success and cleared on expire/reset, so reading it gives you the live response to send to your server for form submission.
|
|
15
|
+
* @example
|
|
16
|
+
* <Captcha r-model:token="token" provider="recaptcha" sitekey="…" />
|
|
17
|
+
*/
|
|
7
18
|
token?: string;
|
|
8
19
|
defaultToken?: string;
|
|
9
20
|
onTokenChange?: (token: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Widget color theme: `light` or `dark` (all three core providers), or `auto` (Turnstile only). Construction-time — re-key the component to change it live.
|
|
23
|
+
*/
|
|
10
24
|
theme?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Widget size. reCAPTCHA/hCaptcha accept `normal`/`compact`/`invisible`; Turnstile accepts `normal`/`compact`/`flexible`. A no-op for Friendly Captcha (its `startMode` analog rides through the `options` escape hatch instead). Construction-time.
|
|
27
|
+
*/
|
|
11
28
|
size?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Optional tab index forwarded to the rendered widget. Omitted from the render config when left unset (`null`).
|
|
31
|
+
*/
|
|
12
32
|
tabindex?: (number) | null;
|
|
33
|
+
/**
|
|
34
|
+
* Escape hatch — provider-specific render options merged last (e.g. Turnstile `action`/`cData`/`retry`, hCaptcha `hl`, reCAPTCHA `badge`, Friendly Captcha `startMode`). Lets you reach keys this component does not promote to first-class props.
|
|
35
|
+
*/
|
|
13
36
|
options?: Record<string, any>;
|
|
14
37
|
onVerify?: (...args: any[]) => void;
|
|
15
38
|
onExpire?: (...args: any[]) => void;
|
|
@@ -24,11 +47,25 @@ declare const Captcha: _$react.ForwardRefExoticComponent<CaptchaProps & _$react.
|
|
|
24
47
|
//#endregion
|
|
25
48
|
//#region src/RecaptchaV3.d.ts
|
|
26
49
|
interface RecaptchaV3Props {
|
|
50
|
+
/**
|
|
51
|
+
* Required. The public reCAPTCHA v3 site key from your Google admin console.
|
|
52
|
+
*/
|
|
27
53
|
sitekey: string;
|
|
54
|
+
/**
|
|
55
|
+
* The default action label reported to reCAPTCHA's risk analysis (e.g. `submit`, `login`). Overridable per call via `execute(action)`.
|
|
56
|
+
*/
|
|
28
57
|
action?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The latest verification token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written on each successful `execute()` — read it to attach the fresh token to your request.
|
|
60
|
+
* @example
|
|
61
|
+
* <RecaptchaV3 r-model:token="token" sitekey="…" action="signup" />
|
|
62
|
+
*/
|
|
29
63
|
token?: string;
|
|
30
64
|
defaultToken?: string;
|
|
31
65
|
onTokenChange?: (token: string) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Opt in to running one `execute()` at mount and emitting `@verify` with the initial token. Off by default — v3 is imperative-first and tokens are short-lived (~2 min), so fetch one at the moment of submission rather than eagerly at mount.
|
|
68
|
+
*/
|
|
32
69
|
executeOnMount?: boolean;
|
|
33
70
|
onError?: (...args: any[]) => void;
|
|
34
71
|
onVerify?: (...args: any[]) => void;
|
package/dist/index.mjs
CHANGED
|
@@ -127,7 +127,6 @@ const Captcha = forwardRef(function Captcha(_props, ref) {
|
|
|
127
127
|
const { provider, sitekey, token, theme, size, tabindex, options, defaultValue, onTokenChange, defaultToken, ...rest } = _props;
|
|
128
128
|
return rest;
|
|
129
129
|
})();
|
|
130
|
-
const disposed = useRef(false);
|
|
131
130
|
const api = useRef(null);
|
|
132
131
|
const widgetId = useRef(null);
|
|
133
132
|
const [token, setToken] = useControllableState({
|
|
@@ -181,9 +180,9 @@ const Captcha = forwardRef(function Captcha(_props, ref) {
|
|
|
181
180
|
return widgetId.current != null && api.current && typeof api.current.getResponse === "function" ? api.current.getResponse(widgetId.current) : "";
|
|
182
181
|
}
|
|
183
182
|
useEffect(() => {
|
|
184
|
-
disposed
|
|
183
|
+
let disposed = false;
|
|
185
184
|
loadCaptchaApi(props.provider).then((a) => {
|
|
186
|
-
if (disposed
|
|
185
|
+
if (disposed) return;
|
|
187
186
|
api.current = a;
|
|
188
187
|
widgetId.current = api.current.render(widgetEl.current, buildConfig());
|
|
189
188
|
}).catch((err) => {
|
|
@@ -193,7 +192,7 @@ const Captcha = forwardRef(function Captcha(_props, ref) {
|
|
|
193
192
|
});
|
|
194
193
|
});
|
|
195
194
|
return () => {
|
|
196
|
-
disposed
|
|
195
|
+
disposed = true;
|
|
197
196
|
if (widgetId.current == null || !api.current) return;
|
|
198
197
|
if (typeof api.current.remove === "function") api.current.remove(widgetId.current);
|
|
199
198
|
else if (typeof api.current.reset === "function") api.current.reset(widgetId.current);
|
|
@@ -311,7 +310,7 @@ const RecaptchaV3 = forwardRef(function RecaptchaV3(_props, ref) {
|
|
|
311
310
|
defaultValue: props.defaultToken ?? "",
|
|
312
311
|
onValueChange: props.onTokenChange
|
|
313
312
|
});
|
|
314
|
-
function execute$1(action
|
|
313
|
+
function execute$1(action) {
|
|
315
314
|
const a = action != null ? action : props.action;
|
|
316
315
|
return loadRecaptchaV3(props.sitekey).then(() => execute(props.sitekey, { action: a })).then((tok) => {
|
|
317
316
|
if (disposed.current) return tok;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozie-ui/captcha-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"description": "Idiomatic React
|
|
7
|
+
"description": "Idiomatic React CAPTCHA — Google reCAPTCHA (v2 + v3), hCaptcha, Cloudflare Turnstile & Friendly Captcha from one Rozie source.",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"rozie",
|
|
10
10
|
"rozie-ui",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"src"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@rozie/runtime-react": "0.
|
|
48
|
+
"@rozie/runtime-react": "0.2.1"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^18.2 || ^19",
|
package/src/Captcha.d.ts
CHANGED
|
@@ -3,14 +3,37 @@ import type { ForwardRefExoticComponent, RefAttributes } from 'react';
|
|
|
3
3
|
import type * as React from 'react';
|
|
4
4
|
|
|
5
5
|
export interface CaptchaProps {
|
|
6
|
+
/**
|
|
7
|
+
* Which widget to render: `recaptcha` (Google reCAPTCHA v2), `hcaptcha`, `turnstile` (Cloudflare), or `friendly` (Friendly Captcha). The first three share a near-identical explicit-render API; Friendly Captcha rides an internal `adapt()` bridge onto the same surface. Construction-time — re-key the component to switch it live.
|
|
8
|
+
*/
|
|
6
9
|
provider?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Required. The public site key from your provider dashboard. Identifies your site to the chosen provider.
|
|
12
|
+
*/
|
|
7
13
|
sitekey: string;
|
|
14
|
+
/**
|
|
15
|
+
* The verified response token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written by the widget on success and cleared on expire/reset, so reading it gives you the live response to send to your server for form submission.
|
|
16
|
+
* @example
|
|
17
|
+
* <Captcha r-model:token="token" provider="recaptcha" sitekey="…" />
|
|
18
|
+
*/
|
|
8
19
|
token?: string;
|
|
9
20
|
defaultToken?: string;
|
|
10
21
|
onTokenChange?: (next: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Widget color theme: `light` or `dark` (all three core providers), or `auto` (Turnstile only). Construction-time — re-key the component to change it live.
|
|
24
|
+
*/
|
|
11
25
|
theme?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Widget size. reCAPTCHA/hCaptcha accept `normal`/`compact`/`invisible`; Turnstile accepts `normal`/`compact`/`flexible`. A no-op for Friendly Captcha (its `startMode` analog rides through the `options` escape hatch instead). Construction-time.
|
|
28
|
+
*/
|
|
12
29
|
size?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Optional tab index forwarded to the rendered widget. Omitted from the render config when left unset (`null`).
|
|
32
|
+
*/
|
|
13
33
|
tabindex?: (number) | null;
|
|
34
|
+
/**
|
|
35
|
+
* Escape hatch — provider-specific render options merged last (e.g. Turnstile `action`/`cData`/`retry`, hCaptcha `hl`, reCAPTCHA `badge`, Friendly Captcha `startMode`). Lets you reach keys this component does not promote to first-class props.
|
|
36
|
+
*/
|
|
14
37
|
options?: Record<string, unknown>;
|
|
15
38
|
onVerify?: (...args: unknown[]) => void;
|
|
16
39
|
onExpire?: (...args: unknown[]) => void;
|
package/src/Captcha.tsx
CHANGED
|
@@ -7,20 +7,41 @@ import { clsx, useControllableState } from '@rozie/runtime-react';
|
|
|
7
7
|
import { loadCaptchaApi } from './internal/loadCaptchaApi';
|
|
8
8
|
|
|
9
9
|
// Live widget handle. Top-level lets → React hoists to useRef (setup-once).
|
|
10
|
-
// `
|
|
11
|
-
//
|
|
12
|
-
// mount-body local, so a `let disposed` inside $onMount is out of scope in the
|
|
13
|
-
// teardown (TS2304). Top-level — like api/widgetId — is visible to both.
|
|
10
|
+
// `api`/`widgetId` MUST be top-level — reset()/execute()/getResponse() (the
|
|
11
|
+
// $expose'd imperative handle, callable any time) read them outside $onMount.
|
|
14
12
|
|
|
15
13
|
interface CaptchaProps {
|
|
14
|
+
/**
|
|
15
|
+
* Which widget to render: `recaptcha` (Google reCAPTCHA v2), `hcaptcha`, `turnstile` (Cloudflare), or `friendly` (Friendly Captcha). The first three share a near-identical explicit-render API; Friendly Captcha rides an internal `adapt()` bridge onto the same surface. Construction-time — re-key the component to switch it live.
|
|
16
|
+
*/
|
|
16
17
|
provider?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Required. The public site key from your provider dashboard. Identifies your site to the chosen provider.
|
|
20
|
+
*/
|
|
17
21
|
sitekey: string;
|
|
22
|
+
/**
|
|
23
|
+
* The verified response token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written by the widget on success and cleared on expire/reset, so reading it gives you the live response to send to your server for form submission.
|
|
24
|
+
* @example
|
|
25
|
+
* <Captcha r-model:token="token" provider="recaptcha" sitekey="…" />
|
|
26
|
+
*/
|
|
18
27
|
token?: string;
|
|
19
28
|
defaultToken?: string;
|
|
20
29
|
onTokenChange?: (token: string) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Widget color theme: `light` or `dark` (all three core providers), or `auto` (Turnstile only). Construction-time — re-key the component to change it live.
|
|
32
|
+
*/
|
|
21
33
|
theme?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Widget size. reCAPTCHA/hCaptcha accept `normal`/`compact`/`invisible`; Turnstile accepts `normal`/`compact`/`flexible`. A no-op for Friendly Captcha (its `startMode` analog rides through the `options` escape hatch instead). Construction-time.
|
|
36
|
+
*/
|
|
22
37
|
size?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Optional tab index forwarded to the rendered widget. Omitted from the render config when left unset (`null`).
|
|
40
|
+
*/
|
|
23
41
|
tabindex?: (number) | null;
|
|
42
|
+
/**
|
|
43
|
+
* Escape hatch — provider-specific render options merged last (e.g. Turnstile `action`/`cData`/`retry`, hCaptcha `hl`, reCAPTCHA `badge`, Friendly Captcha `startMode`). Lets you reach keys this component does not promote to first-class props.
|
|
44
|
+
*/
|
|
24
45
|
options?: Record<string, any>;
|
|
25
46
|
onVerify?: (...args: any[]) => void;
|
|
26
47
|
onExpire?: (...args: any[]) => void;
|
|
@@ -48,7 +69,6 @@ const Captcha = forwardRef<CaptchaHandle, CaptchaProps>(function Captcha(_props:
|
|
|
48
69
|
void provider; void sitekey; void token; void theme; void size; void tabindex; void options; void defaultValue; void onTokenChange; void defaultToken;
|
|
49
70
|
return rest;
|
|
50
71
|
})();
|
|
51
|
-
const disposed = useRef(false);
|
|
52
72
|
const api = useRef<any>(null);
|
|
53
73
|
const widgetId = useRef<any>(null);
|
|
54
74
|
const [token, setToken] = useControllableState({
|
|
@@ -109,9 +129,15 @@ const Captcha = forwardRef<CaptchaHandle, CaptchaProps>(function Captcha(_props:
|
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
useEffect(() => {
|
|
112
|
-
|
|
132
|
+
// Mount-local (not top-level) — read only by this closure's own async
|
|
133
|
+
// .then()/.catch() and the returned teardown below. Emitter-hardening
|
|
134
|
+
// backlog item #2 (project_emitter_hardening_backlog): every target keeps
|
|
135
|
+
// a $onMount setup-local in scope for its own returned teardown, so this
|
|
136
|
+
// no longer needs the prior TOP-LEVEL-`let` workaround (unlike `api`/
|
|
137
|
+
// `widgetId` above, which stay top-level for the unrelated $expose reason).
|
|
138
|
+
let disposed = false;
|
|
113
139
|
loadCaptchaApi(props.provider).then((a: any) => {
|
|
114
|
-
if (disposed
|
|
140
|
+
if (disposed) return;
|
|
115
141
|
api.current = a;
|
|
116
142
|
widgetId.current = api.current.render(widgetEl.current!, buildConfig());
|
|
117
143
|
}).catch((err: any) => {
|
|
@@ -121,7 +147,7 @@ const Captcha = forwardRef<CaptchaHandle, CaptchaProps>(function Captcha(_props:
|
|
|
121
147
|
});
|
|
122
148
|
});
|
|
123
149
|
return () => {
|
|
124
|
-
disposed
|
|
150
|
+
disposed = true;
|
|
125
151
|
if (widgetId.current == null || !api.current) return;
|
|
126
152
|
// Turnstile fully removes a widget; reCAPTCHA/hCaptcha only reset.
|
|
127
153
|
if (typeof api.current.remove === 'function') api.current.remove(widgetId.current);else if (typeof api.current.reset === 'function') api.current.reset(widgetId.current);
|
package/src/RecaptchaV3.d.ts
CHANGED
|
@@ -3,11 +3,25 @@ import type { ForwardRefExoticComponent, RefAttributes } from 'react';
|
|
|
3
3
|
import type * as React from 'react';
|
|
4
4
|
|
|
5
5
|
export interface RecaptchaV3Props {
|
|
6
|
+
/**
|
|
7
|
+
* Required. The public reCAPTCHA v3 site key from your Google admin console.
|
|
8
|
+
*/
|
|
6
9
|
sitekey: string;
|
|
10
|
+
/**
|
|
11
|
+
* The default action label reported to reCAPTCHA's risk analysis (e.g. `submit`, `login`). Overridable per call via `execute(action)`.
|
|
12
|
+
*/
|
|
7
13
|
action?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The latest verification token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written on each successful `execute()` — read it to attach the fresh token to your request.
|
|
16
|
+
* @example
|
|
17
|
+
* <RecaptchaV3 r-model:token="token" sitekey="…" action="signup" />
|
|
18
|
+
*/
|
|
8
19
|
token?: string;
|
|
9
20
|
defaultToken?: string;
|
|
10
21
|
onTokenChange?: (next: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Opt in to running one `execute()` at mount and emitting `@verify` with the initial token. Off by default — v3 is imperative-first and tokens are short-lived (~2 min), so fetch one at the moment of submission rather than eagerly at mount.
|
|
24
|
+
*/
|
|
11
25
|
executeOnMount?: boolean;
|
|
12
26
|
onError?: (...args: unknown[]) => void;
|
|
13
27
|
onVerify?: (...args: unknown[]) => void;
|
package/src/RecaptchaV3.tsx
CHANGED
|
@@ -6,18 +6,34 @@ import { clsx, useControllableState } from '@rozie/runtime-react';
|
|
|
6
6
|
// codegen copies src/internal/ into every leaf, so this import resolves ×6.
|
|
7
7
|
import { loadRecaptchaV3, execute as v3Execute } from './internal/loadRecaptchaV3';
|
|
8
8
|
|
|
9
|
-
// `disposed` MUST be top-level (not
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// `disposed` MUST be top-level (not $onMount-local): the exported `execute()`
|
|
10
|
+
// below — callable any time via `$expose({ execute })`, including after
|
|
11
|
+
// unmount — reads it to guard a late resolve that fires post-unmount. That
|
|
12
|
+
// cross-function visibility (not a per-target emitter limitation) is why this
|
|
13
|
+
// one stays top-level even after emitter-hardening backlog item #2 landed
|
|
14
|
+
// (contrast Captcha.rozie's `disposed`, which IS $onMount-local — its
|
|
15
|
+
// exposed handle functions don't read it).
|
|
14
16
|
|
|
15
17
|
interface RecaptchaV3Props {
|
|
18
|
+
/**
|
|
19
|
+
* Required. The public reCAPTCHA v3 site key from your Google admin console.
|
|
20
|
+
*/
|
|
16
21
|
sitekey: string;
|
|
22
|
+
/**
|
|
23
|
+
* The default action label reported to reCAPTCHA's risk analysis (e.g. `submit`, `login`). Overridable per call via `execute(action)`.
|
|
24
|
+
*/
|
|
17
25
|
action?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The latest verification token (two-way `r-model`). As the sole `model: true` prop it drives the Angular `ControlValueAccessor`. Written on each successful `execute()` — read it to attach the fresh token to your request.
|
|
28
|
+
* @example
|
|
29
|
+
* <RecaptchaV3 r-model:token="token" sitekey="…" action="signup" />
|
|
30
|
+
*/
|
|
18
31
|
token?: string;
|
|
19
32
|
defaultToken?: string;
|
|
20
33
|
onTokenChange?: (token: string) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Opt in to running one `execute()` at mount and emitting `@verify` with the initial token. Off by default — v3 is imperative-first and tokens are short-lived (~2 min), so fetch one at the moment of submission rather than eagerly at mount.
|
|
36
|
+
*/
|
|
21
37
|
executeOnMount?: boolean;
|
|
22
38
|
onError?: (...args: any[]) => void;
|
|
23
39
|
onVerify?: (...args: any[]) => void;
|
|
@@ -52,14 +68,13 @@ const RecaptchaV3 = forwardRef<RecaptchaV3Handle, RecaptchaV3Props>(function Rec
|
|
|
52
68
|
// ref named `token`, and a same-named param shadows it (`token.value = token`
|
|
53
69
|
// would write the param). Use `tok` (mirrors Captcha.rozie's `response`).
|
|
54
70
|
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
function execute(action = null) {
|
|
71
|
+
// A bare `action` (no author default) is fine — the emitter now lowers a
|
|
72
|
+
// TRAILING `$expose` verb param optional (`action?: any`) whenever it sees a
|
|
73
|
+
// fewer-arg internal call to the SAME verb, which the no-arg
|
|
74
|
+
// executeOnMount path (`execute()`) below is (emitter-hardening backlog
|
|
75
|
+
// item #5). The `action = null` author-side default this comment used to
|
|
76
|
+
// require is gone — the compiler owns the arity now, not this source.
|
|
77
|
+
function execute(action?: any) {
|
|
63
78
|
const a = action != null ? action : props.action;
|
|
64
79
|
return loadRecaptchaV3(props.sitekey).then(() => v3Execute(props.sitekey, {
|
|
65
80
|
action: a
|