@silverassist/recaptcha 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-01-27
9
+
10
+ ### Added
11
+
12
+ - Initial release of `@silverassist/recaptcha`
13
+ - `RecaptchaWrapper` client component for automatic token generation
14
+ - Automatic script loading via Next.js `Script` component
15
+ - Token auto-refresh before expiration (90 seconds default)
16
+ - Hidden input field for form submission
17
+ - Configurable callbacks (`onTokenGenerated`, `onError`)
18
+ - Graceful fallback when not configured
19
+ - `validateRecaptcha` server function for token validation
20
+ - Google API verification
21
+ - Score threshold checking
22
+ - Action verification
23
+ - Debug logging option
24
+ - Skip validation when not configured (dev mode)
25
+ - `isRecaptchaEnabled` helper function
26
+ - `getRecaptchaToken` FormData extraction helper
27
+ - Full TypeScript support with exported types
28
+ - `RecaptchaWrapperProps`
29
+ - `RecaptchaValidationResult`
30
+ - `RecaptchaVerifyResponse`
31
+ - `RecaptchaConfig`
32
+ - `RecaptchaValidationOptions`
33
+ - Subpath exports for tree-shaking
34
+ - `@silverassist/recaptcha/client`
35
+ - `@silverassist/recaptcha/server`
36
+ - `@silverassist/recaptcha/types`
37
+ - `@silverassist/recaptcha/constants`
38
+ - Comprehensive test suite with >80% coverage
39
+ - ESM and CommonJS bundle outputs
40
+
41
+ ### Security
42
+
43
+ - Server-side token validation to prevent client-side bypass
44
+ - Action verification to prevent token reuse across different forms
45
+ - Configurable score thresholds for different risk levels
package/LICENSE ADDED
@@ -0,0 +1,135 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree
8
+ to them as both strict obligations and conditions to all
9
+ your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the
14
+ software to do everything you might do with the software
15
+ that would otherwise infringe the licensor's copyright
16
+ in it for any permitted purpose. However, you may
17
+ only distribute the software according to [Distribution
18
+ License](#distribution-license) and make changes or new works
19
+ based on the software according to [Changes and New Works
20
+ License](#changes-and-new-works-license).
21
+
22
+ ## Distribution License
23
+
24
+ The licensor grants you an additional copyright license
25
+ to distribute copies of the software. Your license
26
+ to distribute covers distributing the software with
27
+ changes and new works permitted by [Changes and New Works
28
+ License](#changes-and-new-works-license).
29
+
30
+ ## Notices
31
+
32
+ You must ensure that anyone who gets a copy of any part of
33
+ the software from you also gets a copy of these terms or the
34
+ URL for them above, as well as copies of any plain-text lines
35
+ beginning with `Required Notice:` that the licensor provided
36
+ with the software. For example:
37
+
38
+ > Required Notice: Copyright SilverAssist (https://silverassist.com)
39
+
40
+ ## Changes and New Works License
41
+
42
+ The licensor grants you an additional copyright license to
43
+ make changes and new works based on the software for any
44
+ permitted purpose.
45
+
46
+ ## Patent License
47
+
48
+ The licensor grants you a patent license for the software that
49
+ covers patent claims the licensor can license, or becomes able
50
+ to license, that you would infringe by using the software.
51
+
52
+ ## Noncommercial Purposes
53
+
54
+ Any noncommercial purpose is a permitted purpose.
55
+
56
+ ## Personal Uses
57
+
58
+ Personal use for research, experiment, and testing for
59
+ the benefit of public knowledge, personal study, private
60
+ entertainment, hobby projects, amateur pursuits, or religious
61
+ observance, without any anticipated commercial application,
62
+ is use for a permitted purpose.
63
+
64
+ ## Noncommercial Organizations
65
+
66
+ Use by any charitable organization, educational institution,
67
+ public research organization, public safety or health
68
+ organization, environmental protection organization,
69
+ or government institution is use for a permitted purpose
70
+ regardless of the source of funding or obligations resulting
71
+ from the funding.
72
+
73
+ ## Fair Use
74
+
75
+ You may have "fair use" rights for the software under the
76
+ law. These terms do not limit them.
77
+
78
+ ## No Other Rights
79
+
80
+ These terms do not allow you to sublicense or transfer any of
81
+ your licenses to anyone else, or prevent the licensor from
82
+ granting licenses to anyone else. These terms do not imply
83
+ any other licenses.
84
+
85
+ ## Patent Defense
86
+
87
+ If you make any written claim that the software infringes or
88
+ contributes to infringement of any patent, your patent license
89
+ for the software granted under these terms ends immediately. If
90
+ your company makes such a claim, your patent license ends
91
+ immediately for work on behalf of your company.
92
+
93
+ ## Violations
94
+
95
+ The first time you are notified in writing that you have
96
+ violated any of these terms, or done anything with the software
97
+ not covered by your licenses, your licenses can nonetheless
98
+ continue if you come into full compliance with these terms,
99
+ and take practical steps to correct past violations, within
100
+ 32 days of receiving notice. Otherwise, all your licenses
101
+ end immediately.
102
+
103
+ ## No Liability
104
+
105
+ ***As far as the law allows, the software comes as is, without
106
+ any warranty or condition, and the licensor will not be liable
107
+ to you for any damages arising out of these terms or the use
108
+ or nature of the software, under any kind of legal claim.***
109
+
110
+ ## Definitions
111
+
112
+ The **licensor** is the individual or entity offering these
113
+ terms, and the **software** is the software the licensor makes
114
+ available under these terms.
115
+
116
+ **You** refers to the individual or entity agreeing to these
117
+ terms.
118
+
119
+ **Your company** is any legal entity, sole proprietorship,
120
+ or other kind of organization that you work for, plus all
121
+ organizations that have control over, are under the control of,
122
+ or are under common control with that organization. **Control**
123
+ means ownership of substantially all the assets of an entity,
124
+ or the power to direct its management and policies by vote,
125
+ contract, or otherwise. Control can be direct or indirect.
126
+
127
+ **Your licenses** are all the licenses granted to you for the
128
+ software under these terms.
129
+
130
+ **Use** means anything you do with the software requiring one
131
+ of your licenses.
132
+
133
+ ---
134
+
135
+ Required Notice: Copyright 2026 SilverAssist (https://silverassist.com)
package/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # @silverassist/recaptcha
2
+
3
+ Google reCAPTCHA v3 integration for Next.js applications with Server Actions support.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@silverassist/recaptcha.svg)](https://www.npmjs.com/package/@silverassist/recaptcha)
6
+ [![License](https://img.shields.io/badge/license-PolyForm%20Noncommercial-blue.svg)](LICENSE)
7
+
8
+ ## Features
9
+
10
+ - ✅ **Client Component**: `RecaptchaWrapper` for automatic token generation
11
+ - ✅ **Server Validation**: `validateRecaptcha` function for Server Actions
12
+ - ✅ **TypeScript Support**: Full type definitions included
13
+ - ✅ **Next.js Optimized**: Works with App Router and Server Actions
14
+ - ✅ **Auto Token Refresh**: Tokens refresh automatically before expiration
15
+ - ✅ **Graceful Degradation**: Works in development without credentials
16
+ - ✅ **Configurable Thresholds**: Custom score thresholds per form
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @silverassist/recaptcha
22
+ # or
23
+ yarn add @silverassist/recaptcha
24
+ # or
25
+ pnpm add @silverassist/recaptcha
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ ### 1. Get reCAPTCHA Keys
31
+
32
+ 1. Go to [Google reCAPTCHA Admin](https://www.google.com/recaptcha/admin)
33
+ 2. Create a new site with reCAPTCHA v3
34
+ 3. Get your **Site Key** (public) and **Secret Key** (private)
35
+
36
+ ### 2. Add Environment Variables
37
+
38
+ ```bash
39
+ # .env.local
40
+ NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your_site_key_here
41
+ RECAPTCHA_SECRET_KEY=your_secret_key_here
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Client Component
47
+
48
+ Add `RecaptchaWrapper` inside your form:
49
+
50
+ ```tsx
51
+ "use client";
52
+
53
+ import { RecaptchaWrapper } from "@silverassist/recaptcha";
54
+
55
+ export function ContactForm() {
56
+ return (
57
+ <form action={submitForm}>
58
+ <RecaptchaWrapper action="contact_form" />
59
+ <input name="email" type="email" required />
60
+ <textarea name="message" required />
61
+ <button type="submit">Send</button>
62
+ </form>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ### Server Action
68
+
69
+ Validate the token in your Server Action:
70
+
71
+ ```ts
72
+ "use server";
73
+
74
+ import { validateRecaptcha, getRecaptchaToken } from "@silverassist/recaptcha/server";
75
+
76
+ export async function submitForm(formData: FormData) {
77
+ // Get and validate reCAPTCHA token
78
+ const token = getRecaptchaToken(formData);
79
+ const recaptcha = await validateRecaptcha(token, "contact_form");
80
+
81
+ if (!recaptcha.success) {
82
+ return { success: false, message: recaptcha.error };
83
+ }
84
+
85
+ // Process form data...
86
+ const email = formData.get("email");
87
+ const message = formData.get("message");
88
+
89
+ // Your form processing logic here
90
+
91
+ return { success: true };
92
+ }
93
+ ```
94
+
95
+ ## API Reference
96
+
97
+ ### RecaptchaWrapper
98
+
99
+ Client component that loads reCAPTCHA and generates tokens.
100
+
101
+ ```tsx
102
+ <RecaptchaWrapper
103
+ action="contact_form" // Required: action name for analytics
104
+ inputName="recaptchaToken" // Optional: hidden input name (default: "recaptchaToken")
105
+ inputId="recaptcha-token" // Optional: hidden input id
106
+ siteKey="..." // Optional: override env variable
107
+ refreshInterval={90000} // Optional: token refresh interval in ms (default: 90000)
108
+ onTokenGenerated={(token) => {}} // Optional: callback when token is generated
109
+ onError={(error) => {}} // Optional: callback on error
110
+ />
111
+ ```
112
+
113
+ ### validateRecaptcha
114
+
115
+ Server-side token validation function.
116
+
117
+ ```ts
118
+ const result = await validateRecaptcha(
119
+ token, // Token from form
120
+ "contact_form", // Expected action (optional)
121
+ {
122
+ scoreThreshold: 0.5, // Minimum score (default: 0.5)
123
+ secretKey: "...", // Override env variable
124
+ debug: true, // Enable debug logging
125
+ }
126
+ );
127
+
128
+ // Result type:
129
+ // {
130
+ // success: boolean,
131
+ // score: number,
132
+ // error?: string,
133
+ // skipped?: boolean,
134
+ // rawResponse?: RecaptchaVerifyResponse
135
+ // }
136
+ ```
137
+
138
+ ### isRecaptchaEnabled
139
+
140
+ Check if reCAPTCHA is configured.
141
+
142
+ ```ts
143
+ import { isRecaptchaEnabled } from "@silverassist/recaptcha/server";
144
+
145
+ if (isRecaptchaEnabled()) {
146
+ // Validate token
147
+ } else {
148
+ // Skip validation (development)
149
+ }
150
+ ```
151
+
152
+ ### getRecaptchaToken
153
+
154
+ Extract token from FormData.
155
+
156
+ ```ts
157
+ import { getRecaptchaToken } from "@silverassist/recaptcha/server";
158
+
159
+ const token = getRecaptchaToken(formData);
160
+ const token = getRecaptchaToken(formData, "customFieldName");
161
+ ```
162
+
163
+ ## Score Thresholds
164
+
165
+ reCAPTCHA v3 returns a score from 0.0 to 1.0:
166
+
167
+ | Score | Meaning |
168
+ |-------|---------|
169
+ | 1.0 | Very likely human |
170
+ | 0.7+ | Likely human |
171
+ | 0.5 | Default threshold |
172
+ | 0.3- | Suspicious |
173
+ | 0.0 | Very likely bot |
174
+
175
+ Adjust threshold based on form sensitivity:
176
+
177
+ ```ts
178
+ // Standard forms
179
+ await validateRecaptcha(token, "contact", { scoreThreshold: 0.5 });
180
+
181
+ // Sensitive forms (payments, account creation)
182
+ await validateRecaptcha(token, "payment", { scoreThreshold: 0.7 });
183
+
184
+ // Low-risk forms (newsletter signup)
185
+ await validateRecaptcha(token, "newsletter", { scoreThreshold: 0.3 });
186
+ ```
187
+
188
+ ## Subpath Imports
189
+
190
+ You can import from specific subpaths for better tree-shaking:
191
+
192
+ ```ts
193
+ // Main exports (client + server + types)
194
+ import { RecaptchaWrapper, validateRecaptcha } from "@silverassist/recaptcha";
195
+
196
+ // Client only
197
+ import { RecaptchaWrapper } from "@silverassist/recaptcha/client";
198
+
199
+ // Server only
200
+ import { validateRecaptcha, getRecaptchaToken, isRecaptchaEnabled } from "@silverassist/recaptcha/server";
201
+
202
+ // Types only
203
+ import type { RecaptchaValidationResult, RecaptchaWrapperProps } from "@silverassist/recaptcha/types";
204
+
205
+ // Constants only
206
+ import { DEFAULT_SCORE_THRESHOLD, RECAPTCHA_CONFIG } from "@silverassist/recaptcha/constants";
207
+ ```
208
+
209
+ ## Development
210
+
211
+ In development, when `RECAPTCHA_SECRET_KEY` is not set, validation is skipped and forms work normally. This allows testing without reCAPTCHA credentials.
212
+
213
+ ```ts
214
+ const result = await validateRecaptcha(token, "test");
215
+ // Returns: { success: true, score: 1, skipped: true }
216
+ ```
217
+
218
+ ## TypeScript
219
+
220
+ Full TypeScript support with exported types:
221
+
222
+ ```ts
223
+ import type {
224
+ RecaptchaWrapperProps,
225
+ RecaptchaValidationResult,
226
+ RecaptchaVerifyResponse,
227
+ RecaptchaConfig,
228
+ RecaptchaValidationOptions,
229
+ } from "@silverassist/recaptcha";
230
+ ```
231
+
232
+ ## License
233
+
234
+ [Polyform Noncommercial License 1.0.0](LICENSE)
235
+
236
+ ## Links
237
+
238
+ - [GitHub Repository](https://github.com/SilverAssist/recaptcha)
239
+ - [npm Package](https://www.npmjs.com/package/@silverassist/recaptcha)
240
+ - [Google reCAPTCHA v3 Documentation](https://developers.google.com/recaptcha/docs/v3)
@@ -0,0 +1,75 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * Props for RecaptchaWrapper client component
5
+ */
6
+ interface RecaptchaWrapperProps {
7
+ /** Action name for reCAPTCHA analytics (e.g., "contact_form", "signup") */
8
+ action: string;
9
+ /** Name attribute for the hidden input (default: "recaptchaToken") */
10
+ inputName?: string;
11
+ /** ID attribute for the hidden input */
12
+ inputId?: string;
13
+ /** Override site key (default: uses NEXT_PUBLIC_RECAPTCHA_SITE_KEY) */
14
+ siteKey?: string;
15
+ /** Token refresh interval in ms (default: 90000 = 90 seconds) */
16
+ refreshInterval?: number;
17
+ /** Callback when token is generated */
18
+ onTokenGenerated?: (token: string) => void;
19
+ /** Callback when an error occurs */
20
+ onError?: (error: Error) => void;
21
+ }
22
+ /**
23
+ * Global window interface extension for reCAPTCHA
24
+ */
25
+ declare global {
26
+ interface Window {
27
+ grecaptcha: {
28
+ ready: (callback: () => void) => void;
29
+ execute: (siteKey: string, options: {
30
+ action: string;
31
+ }) => Promise<string>;
32
+ };
33
+ }
34
+ }
35
+
36
+ /**
37
+ * RecaptchaWrapper - Client component for reCAPTCHA v3 integration
38
+ *
39
+ * Features:
40
+ * - Loads reCAPTCHA script automatically
41
+ * - Generates token when script loads
42
+ * - Refreshes token periodically (tokens expire after 2 minutes)
43
+ * - Stores token in hidden input field for form submission
44
+ * - Graceful fallback when not configured
45
+ *
46
+ * @example Basic usage
47
+ * ```tsx
48
+ * <form action={formAction}>
49
+ * <RecaptchaWrapper action="contact_form" />
50
+ * <input name="email" type="email" required />
51
+ * <button type="submit">Submit</button>
52
+ * </form>
53
+ * ```
54
+ *
55
+ * @example Custom input name
56
+ * ```tsx
57
+ * <RecaptchaWrapper
58
+ * action="signup"
59
+ * inputName="captchaToken"
60
+ * inputId="signup-captcha"
61
+ * />
62
+ * ```
63
+ *
64
+ * @example With callbacks
65
+ * ```tsx
66
+ * <RecaptchaWrapper
67
+ * action="payment"
68
+ * onTokenGenerated={(token) => console.log("Token:", token)}
69
+ * onError={(error) => console.error("Error:", error)}
70
+ * />
71
+ * ```
72
+ */
73
+ declare function RecaptchaWrapper({ action, inputName, inputId, siteKey: propSiteKey, refreshInterval, onTokenGenerated, onError, }: RecaptchaWrapperProps): react_jsx_runtime.JSX.Element | null;
74
+
75
+ export { RecaptchaWrapper, RecaptchaWrapper as default };
@@ -0,0 +1,75 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * Props for RecaptchaWrapper client component
5
+ */
6
+ interface RecaptchaWrapperProps {
7
+ /** Action name for reCAPTCHA analytics (e.g., "contact_form", "signup") */
8
+ action: string;
9
+ /** Name attribute for the hidden input (default: "recaptchaToken") */
10
+ inputName?: string;
11
+ /** ID attribute for the hidden input */
12
+ inputId?: string;
13
+ /** Override site key (default: uses NEXT_PUBLIC_RECAPTCHA_SITE_KEY) */
14
+ siteKey?: string;
15
+ /** Token refresh interval in ms (default: 90000 = 90 seconds) */
16
+ refreshInterval?: number;
17
+ /** Callback when token is generated */
18
+ onTokenGenerated?: (token: string) => void;
19
+ /** Callback when an error occurs */
20
+ onError?: (error: Error) => void;
21
+ }
22
+ /**
23
+ * Global window interface extension for reCAPTCHA
24
+ */
25
+ declare global {
26
+ interface Window {
27
+ grecaptcha: {
28
+ ready: (callback: () => void) => void;
29
+ execute: (siteKey: string, options: {
30
+ action: string;
31
+ }) => Promise<string>;
32
+ };
33
+ }
34
+ }
35
+
36
+ /**
37
+ * RecaptchaWrapper - Client component for reCAPTCHA v3 integration
38
+ *
39
+ * Features:
40
+ * - Loads reCAPTCHA script automatically
41
+ * - Generates token when script loads
42
+ * - Refreshes token periodically (tokens expire after 2 minutes)
43
+ * - Stores token in hidden input field for form submission
44
+ * - Graceful fallback when not configured
45
+ *
46
+ * @example Basic usage
47
+ * ```tsx
48
+ * <form action={formAction}>
49
+ * <RecaptchaWrapper action="contact_form" />
50
+ * <input name="email" type="email" required />
51
+ * <button type="submit">Submit</button>
52
+ * </form>
53
+ * ```
54
+ *
55
+ * @example Custom input name
56
+ * ```tsx
57
+ * <RecaptchaWrapper
58
+ * action="signup"
59
+ * inputName="captchaToken"
60
+ * inputId="signup-captcha"
61
+ * />
62
+ * ```
63
+ *
64
+ * @example With callbacks
65
+ * ```tsx
66
+ * <RecaptchaWrapper
67
+ * action="payment"
68
+ * onTokenGenerated={(token) => console.log("Token:", token)}
69
+ * onError={(error) => console.error("Error:", error)}
70
+ * />
71
+ * ```
72
+ */
73
+ declare function RecaptchaWrapper({ action, inputName, inputId, siteKey: propSiteKey, refreshInterval, onTokenGenerated, onError, }: RecaptchaWrapperProps): react_jsx_runtime.JSX.Element | null;
74
+
75
+ export { RecaptchaWrapper, RecaptchaWrapper as default };
@@ -0,0 +1,115 @@
1
+ "use client";
2
+
3
+ 'use strict';
4
+
5
+ Object.defineProperty(exports, '__esModule', { value: true });
6
+
7
+ var Script = require('next/script');
8
+ var react = require('react');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var Script__default = /*#__PURE__*/_interopDefault(Script);
14
+
15
+ var DEFAULT_TOKEN_REFRESH_INTERVAL = 9e4;
16
+ var RECAPTCHA_CONFIG = {
17
+ /** Default token refresh interval */
18
+ tokenRefreshInterval: DEFAULT_TOKEN_REFRESH_INTERVAL
19
+ };
20
+ function RecaptchaWrapper({
21
+ action,
22
+ inputName = "recaptchaToken",
23
+ inputId = "recaptcha-token",
24
+ siteKey: propSiteKey,
25
+ refreshInterval = RECAPTCHA_CONFIG.tokenRefreshInterval,
26
+ onTokenGenerated,
27
+ onError
28
+ }) {
29
+ const siteKey = propSiteKey ?? process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
30
+ const tokenInputRef = react.useRef(null);
31
+ const refreshIntervalRef = react.useRef(null);
32
+ const executeRecaptcha = react.useCallback(async () => {
33
+ if (!siteKey) {
34
+ return;
35
+ }
36
+ try {
37
+ if (typeof window !== "undefined" && window.grecaptcha) {
38
+ window.grecaptcha.ready(async () => {
39
+ try {
40
+ const token = await window.grecaptcha.execute(siteKey, { action });
41
+ if (tokenInputRef.current) {
42
+ tokenInputRef.current.value = token;
43
+ }
44
+ if (onTokenGenerated) {
45
+ onTokenGenerated(token);
46
+ }
47
+ } catch (error) {
48
+ console.error("[reCAPTCHA] Error executing reCAPTCHA:", error);
49
+ if (onError && error instanceof Error) {
50
+ onError(error);
51
+ }
52
+ }
53
+ });
54
+ }
55
+ } catch (error) {
56
+ console.error("[reCAPTCHA] Error:", error);
57
+ if (onError && error instanceof Error) {
58
+ onError(error);
59
+ }
60
+ }
61
+ }, [siteKey, action, onTokenGenerated, onError]);
62
+ react.useEffect(() => {
63
+ executeRecaptcha();
64
+ refreshIntervalRef.current = setInterval(() => {
65
+ executeRecaptcha();
66
+ }, refreshInterval);
67
+ return () => {
68
+ if (refreshIntervalRef.current) {
69
+ clearInterval(refreshIntervalRef.current);
70
+ }
71
+ };
72
+ }, [executeRecaptcha, refreshInterval]);
73
+ if (!siteKey) {
74
+ if (process.env.NODE_ENV === "development") {
75
+ console.warn(
76
+ "[reCAPTCHA] Site key not configured. Set NEXT_PUBLIC_RECAPTCHA_SITE_KEY environment variable."
77
+ );
78
+ }
79
+ return null;
80
+ }
81
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
82
+ /* @__PURE__ */ jsxRuntime.jsx(
83
+ "input",
84
+ {
85
+ ref: tokenInputRef,
86
+ type: "hidden",
87
+ name: inputName,
88
+ id: inputId,
89
+ "data-testid": "recaptcha-token-input"
90
+ }
91
+ ),
92
+ /* @__PURE__ */ jsxRuntime.jsx(
93
+ Script__default.default,
94
+ {
95
+ src: `https://www.google.com/recaptcha/api.js?render=${siteKey}`,
96
+ strategy: "afterInteractive",
97
+ onLoad: () => {
98
+ executeRecaptcha();
99
+ },
100
+ onError: () => {
101
+ console.error("[reCAPTCHA] Failed to load reCAPTCHA script");
102
+ if (onError) {
103
+ onError(new Error("Failed to load reCAPTCHA script"));
104
+ }
105
+ }
106
+ }
107
+ )
108
+ ] });
109
+ }
110
+ var client_default = RecaptchaWrapper;
111
+
112
+ exports.RecaptchaWrapper = RecaptchaWrapper;
113
+ exports.default = client_default;
114
+ //# sourceMappingURL=index.js.map
115
+ //# sourceMappingURL=index.js.map