├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ └── issue-template.md
└── workflows
│ └── standard-ci.yml
├── .gitignore
├── .npmignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── MIGRATION.md
├── README.md
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── index.js
├── recaptcha-wrapper.js
└── recaptcha.js
└── test
├── .eslintrc
├── index.js
├── recaptcha-wrapper.spec.js
└── recaptcha.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/react",
4 | [ "@babel/env", { "loose": true } ]
5 | ],
6 | "env": {
7 | "esm": {
8 | "presets": [
9 | [ "@babel/env", { "loose": true, "modules": false } ]
10 | ]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | [*.js]
12 | charset = utf-8
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [{package.json,.travis.yml}]
17 | indent_style = space
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib/**
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /* eslint-env node */
3 | module.exports = {
4 | extends: ["eslint:recommended", "plugin:react/recommended", "prettier"],
5 | parserOptions: {
6 | sourceType: "module",
7 | ecmaVersion: "latest",
8 | ecmaFeatures: {
9 | jsx: true,
10 | },
11 | },
12 | env: {
13 | es6: true,
14 | browser: true,
15 | },
16 | plugins: ["react", "prettier"],
17 | rules: {
18 | "prettier/prettier": "error",
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Issue template
3 | about: Standard Issue Template
4 |
5 | ---
6 |
7 | react-google-recaptcha version: X.X.X
8 | react-async-script version: Y.Y.Y
9 |
--------------------------------------------------------------------------------
/.github/workflows/standard-ci.yml:
--------------------------------------------------------------------------------
1 |
2 | name: standard-ci
3 |
4 | on:
5 | pull_request:
6 | paths-ignore:
7 | - '**.md'
8 | push:
9 | branches:
10 | - master
11 | paths-ignore:
12 | - '**.md'
13 |
14 | jobs:
15 | test:
16 | strategy:
17 | matrix:
18 | node: [14, 16, 18]
19 | os: [ubuntu-latest, windows-latest, macos-latest]
20 | name: Node v${{ matrix.node }} - ${{ matrix.os }}
21 | runs-on: ${{ matrix.os }}
22 | steps:
23 | - uses: actions/checkout@v3
24 |
25 | - uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node }}
28 | cache: 'npm'
29 |
30 | - name: Install dependencies
31 | run: npm ci
32 |
33 | - name: Test
34 | run: npm test
35 |
36 | - name: Lint
37 | run: npm run lint
38 |
39 | - name: Build
40 | run: npm run build
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /lib/
3 | *.log
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | test/
3 | tools/
4 | .gitignore
5 | .travis.yml
6 | karma.conf.js
7 | .babelrc
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "all",
4 | "parser": "babel"
5 | }
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | v3.1.0 - Sat 3 Jun 2023 0:30 ET
2 | ---------------------------------------
3 | - Add Enterprise Support 1 [(#239)](https://github.com/dozoisch/react-google-recaptcha/issues/239)
4 | - Add Enterprise Support 2 [(#270)](https://github.com/dozoisch/react-google-recaptcha/issues/270)
5 | - Add Nonce Support [(#221)](https://github.com/dozoisch/react-google-recaptcha/issues/221)
6 | - Update dependencies 1 [(#206)](https://github.com/dozoisch/react-google-recaptcha/issues/206)
7 | - Update dependencies 2 [(#207)](https://github.com/dozoisch/react-google-recaptcha/issues/207)
8 | - Update dependencies 3 [(#210)](https://github.com/dozoisch/react-google-recaptcha/issues/210)
9 | - Update dependencies 4 [(#211)](https://github.com/dozoisch/react-google-recaptcha/issues/211)
10 | - Update dependencies 5 [(#219)](https://github.com/dozoisch/react-google-recaptcha/issues/219)
11 | - Update dependencies 6 [(#226)](https://github.com/dozoisch/react-google-recaptcha/issues/226)
12 | - Update dependencies 7 [(#229)](https://github.com/dozoisch/react-google-recaptcha/issues/229)
13 | - Update dependencies 8 [(#235)](https://github.com/dozoisch/react-google-recaptcha/issues/235)
14 | - Update dependencies 9 [(#245)](https://github.com/dozoisch/react-google-recaptcha/issues/245)
15 |
16 |
17 | v3.0.0-alpha.1 - Mon 23 Nov 2020 22:25 ET
18 | ---------------------------------------
19 | - Add isolated prop [(#179)](https://github.com/dozoisch/react-google-recaptcha/issues/179)
20 | - Update dependencies 1 [(#187)](https://github.com/dozoisch/react-google-recaptcha/issues/187)
21 | - Update dependencies 2 [(#194)](https://github.com/dozoisch/react-google-recaptcha/issues/194)
22 | - Remove Timeout Removal [(#196)](https://github.com/dozoisch/react-google-recaptcha/issues/196)
23 |
24 |
25 |
26 | v2.1.0 - Fri 5 Jun 2020 22:05 PST
27 | ---------------------------------------
28 | - Add promise based Execution [(#163)](https://github.com/dozoisch/react-google-recaptcha/issues/163)
29 |
30 |
31 |
32 | v2.0.1 - Sat 14 Sep 2019 12:00 PST
33 | ---------------------------------------
34 |
35 | - Fix onChange was using the function passed at mount time [(#154)](https://github.com/dozoisch/react-google-recaptcha/issues/154)
36 |
37 |
38 |
39 | v2.0.0-rc.1 - Sat 3 Aug 2019 8:00 PST
40 | ---------------------------------------
41 |
42 | - Remove lang and removeOnUnmount global options [(#143)](https://github.com/dozoisch/react-google-recaptcha/issues/143)
43 | - Upgrade to node 8, 10, 12 [(#143)](https://github.com/dozoisch/react-google-recaptcha/issues/143)
44 | - Update to react-async-script 1.1 [(#143)](https://github.com/dozoisch/react-google-recaptcha/issues/143)
45 |
46 |
47 | v1.1.0 - Sun 14 Jul 2019 10:56 PST
48 | ---------------------------------------
49 |
50 | - Add HL prop [(#141)](https://github.com/dozoisch/react-google-recaptcha/issues/141)
51 | - Upgrade babel and jest [(#140)](https://github.com/dozoisch/react-google-recaptcha/issues/140)
52 |
53 |
54 |
55 | v1.0.5 - Mon 12 Nov 2018 9:25 PST
56 | ---------------------------------------
57 |
58 | - Fix promise timeout [(#120)](https://github.com/dozoisch/react-google-recaptcha/issues/120)
59 |
60 |
61 |
62 | v1.0.4 - Thu 27 Sep 2018 15:14 PST
63 | ---------------------------------------
64 |
65 | - add .babelrc to .npmignore [(#111)](https://github.com/dozoisch/react-google-recaptcha/issues/111)
66 |
67 |
68 |
69 | v1.0.2 - Wed, 5 Sep 2018 13:21:00 EST
70 | ---------------------------------------
71 |
72 | - fixe unbound onErrored handler [(#104)](https://github.com/dozoisch/react-google-recaptcha/pull/104)
73 |
74 |
75 |
76 | v1.0.1 - Thu, 30 Aug 2018 12:54:00 EST
77 | ---------------------------------------
78 |
79 | - Make the onChange prop not required [(#102)](https://github.com/dozoisch/react-google-recaptcha/pull/102)
80 |
81 |
82 |
83 | v1.0.0 - Fri, 17 Aug 2018 18:11:00 PST
84 | ---------------------------------------
85 |
86 | - Update to react-async-script 1.0 [(#94)](https://github.com/dozoisch/react-google-recaptcha/pull/94)
87 | - Add on Error [(#97)](https://github.com/dozoisch/react-google-recaptcha/pull/97)
88 | - Update build tools
89 | - Node 8 [(#95)](https://github.com/dozoisch/react-google-recaptcha/pull/95)
90 | - Jest [(#95)](https://github.com/dozoisch/react-google-recaptcha/pull/95)
91 | - Eslint/Prettier [(#96)](https://github.com/dozoisch/react-google-recaptcha/pull/96)
92 |
93 |
94 |
95 | v0.14.0 - Sun, 29 Jul 2018 19:20:03 GMT
96 | ---------------------------------------
97 |
98 | - [701695b](../../commit/701695b) [changed] dynamic url creation to allow language change
99 |
100 |
101 |
102 | v0.13.0 - Tue, 24 Jul 2018 21:50:35 GMT
103 | ---------------------------------------
104 |
105 | - [fde0d51](../../commit/fde0d51) [fixed] Update async-script to get rid of Map polyfill
106 |
107 |
108 |
109 | v0.12.0 - Mon, 11 Jun 2018 16:39:53 GMT
110 | ---------------------------------------
111 |
112 | - [f465421](../../commit/f465421) [fixed] react-async-script to dependency
113 | - [48a5726](../../commit/48a5726) [added] Parameter to use recaptchanet instead of google.com
114 |
115 |
116 |
117 | v0.11.1 - Thu, 10 May 2018 22:28:19 GMT
118 | ---------------------------------------
119 |
120 | - [b2ae438](../../commit/b2ae438) [fixed] issue grecaptcha.render is not a function by adding condition
121 |
122 |
123 |
124 | v0.11.0 - Sun, 25 Mar 2018 20:09:27 GMT
125 | ---------------------------------------
126 |
127 | - [4920312](../../commit/4920312) Add es2015 module build (#59)
128 |
129 |
130 |
131 |
132 | v0.10.0 - Sun, 25 Mar 2018 20:02:26 GMT
133 | ---------------------------------------
134 |
135 | - [8c6a8dc](../../commit/8c6a8dc) Remove rendered DOM on unmount (#73)
136 |
137 |
138 |
139 | v0.9.8 - Tue, 28 Nov 2017 04:07:05 GMT
140 | --------------------------------------
141 |
142 | - [7cd1254](../../commit/7cd1254) Fix peerDependency of react
143 |
144 |
145 |
146 |
147 | v0.9.7 - Thu, 10 Aug 2017 00:04:43 GMT
148 | --------------------------------------
149 |
150 | - [e5f6fd9](../../commit/e5f6fd9) Add an accessor for widgetid (#53)
151 | - [8932900](../../commit/8932900) Fixed PropTypes warning due to extra space (#51)
152 |
153 |
154 |
155 | v0.9.6 - Tue, 20 Jun 2017 16:20:26 GMT
156 | --------------------------------------
157 |
158 | - [9796adb](../../commit/9796adb) Fix race on execute() (#49)
159 |
160 |
161 |
162 |
163 | v0.9.5 - Fri, 02 Jun 2017 09:26:50 GMT
164 | --------------------------------------
165 |
166 | - Fixed release
167 |
168 |
169 | v0.9.4 - Fri, 02 Jun 2017 09:26:02 GMT
170 | --------------------------------------
171 |
172 | - [89a1c0e](../../commit/89a1c0e) Changed ref to use function instead of dep strings
173 | - [5c8a04d](../../commit/5c8a04d) Update examples in readme (#47)
174 | - [349aaa8](../../commit/349aaa8) Modified description of badge parameter (#46)
175 |
176 |
177 |
178 | v0.9.3 - Mon, 24 Apr 2017 16:31:56 GMT
179 | --------------------------------------
180 |
181 | - [cb041a3](../../commit/cb041a3) [fixed] issue with handleExpired not being bound
182 |
183 |
184 |
185 | v0.9.2 - Thu, 20 Apr 2017 01:10:09 GMT
186 | --------------------------------------
187 |
188 | - [1c8d411](../../commit/1c8d411) [fixed] updated async-script
189 |
190 |
191 |
192 | v0.9.1 - Tue, 18 Apr 2017 08:27:28 GMT
193 | --------------------------------------
194 |
195 | - [242faf5](../../commit/242faf5) [fixed] space in peer dep version
196 |
197 |
198 |
199 | v0.9.0 - Sun, 16 Apr 2017 23:36:41 GMT
200 | --------------------------------------
201 |
202 | - [bbf5312](../../commit/bbf5312) [changed] updated for react 15.5
203 |
204 |
205 |
206 | v0.8.1 - Mon, 03 Apr 2017 04:34:00 GMT
207 | --------------------------------------
208 |
209 | - [dbd3a47](../../commit/dbd3a47) [fixed] react-async-script dep version
210 |
211 |
212 |
213 | v0.8.0 - Fri, 24 Mar 2017 00:54:19 GMT
214 | --------------------------------------
215 |
216 | - [883210e](../../commit/883210e) Added support for badge attribute (#36)
217 |
218 |
219 |
220 |
221 | v0.7.0 - Thu, 16 Mar 2017 17:11:15 GMT
222 | --------------------------------------
223 |
224 | - [4eda897](../../commit/4eda897) Added Invisible example to README.md
225 | - [34d5e0c](../../commit/34d5e0c) Add invisible props and execute method (#34)
226 | - [cbfe092](../../commit/cbfe092) [added] install of react-async-script in readme
227 |
228 |
229 |
230 | v0.6.0 - Sun, 05 Mar 2017 02:45:00 GMT
231 | --------------------------------------
232 |
233 | - [3c92d6d](../../commit/3c92d6d) [changed] Updated babel, react and dropped 0.10, 0.12 (#30)
234 | - [7c8424c](../../commit/7c8424c) make dependencies more friendly for consuming packages (#29)
235 |
236 |
237 |
238 | v0.5.4 - Tue, 19 Jul 2016 21:46:12 GMT
239 | --------------------------------------
240 |
241 | - [cb679d7](../../commit/cb679d7) [fixed] issue with react 15.2 warnings for unknown props
242 |
243 |
244 |
245 | v0.5.3 - Tue, 03 May 2016 01:57:36 GMT
246 | --------------------------------------
247 |
248 | - [50e770f](../../commit/50e770f) [added] temporary solution to the lang issue
249 |
250 |
251 |
252 | v0.5.2 - Sat, 28 Nov 2015 00:47:54 GMT
253 | --------------------------------------
254 |
255 | - [0e94a8a](../../commit/0e94a8a) [added] stoken parameter
256 |
257 |
258 |
259 | v0.5.1 - Fri, 06 Nov 2015 16:17:16 GMT
260 | --------------------------------------
261 |
262 | - [b7cfa5c](../../commit/b7cfa5c) [fixed] handle widgetId equal to 0
263 |
264 |
265 |
266 | v0.5.0 - Thu, 15 Oct 2015 21:38:43 GMT
267 | --------------------------------------
268 |
269 | - [d217dd1](../../commit/d217dd1) [changed] updated all deps
270 | - [fc3350a](../../commit/fc3350a) [added] mt-changelog and release-script
271 | - [dfa9bf2](../../commit/dfa9bf2) [added] doc for react 0.14 + old 0.13
272 |
273 |
274 |
275 | # 0.4.0
276 | - Added Size Props |Merge #5
277 | - Fixed bug with refs
278 | - Bumps deps
279 |
280 | ## 0.3.2
281 | - Bump deps
282 |
283 | ## 0.3.1
284 | - Added babel runtime to deps
285 | - [#1] Removed unused use strict
286 | - Bump deps
287 |
288 | ## 0.3.0
289 | - Can now uses the recaptcha functions `getValue` `reset` directly from Wrapper without `getComponent` first.
290 | - bump deps
291 |
292 | ## 0.2.2
293 | - Bump react-async-script (bug 0.2.1)
294 |
295 | ## 0.2.1
296 | - Small lint fixes
297 | - Updated react-async-script
298 |
299 | ## 0.2.0
300 | - Now loads the script using react-async-script! Usage is now way simpler
301 |
302 | ## 0.1.0
303 | - Migrated to ES6
304 | - Added Travis
305 | - Added Babel
306 | - Added Karma
307 | - Added First test
308 |
309 | ## 0.0.1
310 | - Initial commit of component
311 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Hugo Dozois
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/MIGRATION.md:
--------------------------------------------------------------------------------
1 | # Migrations
2 |
3 | ## Migrate from 1.0 to 2.0
4 |
5 | - __options.removeOnUnmount__: *REMOVED* This was only useful for the lang changes. Lang is now changed through the `hl` prop.
6 | - __options.lang__: *REMOVED* Instead pass it as the `hl` prop on the component.
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-google-recaptcha
2 |
3 | [![Build Status][ci.img]][ci.url] [![npm version][npm.img]][npm.url] [![npm downloads][npm.dl.img]][npm.dl.url]
4 |
5 | [](https://codesandbox.io/s/1y4zzjq37l)
6 |
7 | React component for [Google reCAPTCHA v2][reCAPTCHA].
8 |
9 | ## Installation
10 |
11 | ```shell
12 | npm install --save react-google-recaptcha
13 | ```
14 |
15 | ## Usage
16 |
17 | All you need to do is [sign up for an API key pair][signup]. You will need the client key then you can use ``.
18 |
19 | The default usage imports a wrapped component that loads the google recaptcha script asynchronously then instantiates a `reCAPTCHA` the user can then interact with.
20 |
21 | Code Example:
22 | ```jsx
23 | import ReCAPTCHA from "react-google-recaptcha";
24 |
25 | function onChange(value) {
26 | console.log("Captcha value:", value);
27 | }
28 |
29 | ReactDOM.render(
30 | ,
34 | document.body
35 | );
36 | ```
37 |
38 | ### Component Props
39 |
40 | Properties used to customise the rendering:
41 |
42 | | Name | Type | Description |
43 | |:---- | ---- | ------ |
44 | | asyncScriptOnLoad | func | *optional* callback when the google recaptcha script has been loaded |
45 | | badge | enum | *optional* `bottomright`, `bottomleft` or `inline`. Positions reCAPTCHA badge. *Only for invisible reCAPTCHA* |
46 | | hl | string | *optional* set the hl parameter, which allows the captcha to be used from different languages, see [reCAPTCHA hl] |
47 | | isolated | bool | *optional* For plugin owners to not interfere with existing reCAPTCHA installations on a page. If true, this reCAPTCHA instance will be part of a separate ID space. *(__default:__ `false`)*
48 | | onChange | func | The function to be called when the user successfully completes the captcha |
49 | | onErrored | func | *optional* callback when the challenge errored, most likely due to network issues. |
50 | | onExpired | func | *optional* callback when the challenge is expired and has to be redone by user. By default it will call the onChange with null to signify expired callback. |
51 | | sitekey | string | The API client key |
52 | | size | enum | *optional* `compact`, `normal` or `invisible`. This allows you to change the size or do an invisible captcha |
53 | | stoken | string | *optional* set the stoken parameter, which allows the captcha to be used from different domains, see [reCAPTCHA secure-token] |
54 | | tabindex | number | *optional* The tabindex on the element *(__default:__ `0`)*
55 | | type | enum | *optional* `image` or `audio` The type of initial captcha *(__defaults:__ `image`)*
56 | | theme | enum | *optional* `light` or `dark` The theme of the widget *(__defaults:__ `light`)*. See [example][docs_theme]
57 |
58 | ### Component Instance API
59 |
60 | The component instance also has some utility functions that can be called. These can be accessed via `ref`.
61 |
62 | - `getValue()` returns the value of the captcha field
63 | - `getWidgetId()` returns the recaptcha widget Id
64 | - `reset()` forces reset. See the [JavaScript API doc][js_api]
65 | - `execute()` programmatically invoke the challenge
66 | - need to call when using `"invisible"` reCAPTCHA - [example below](#invisible-recaptcha)
67 | - `executeAsync()` programmatically invoke the challenge and return a promise that resolves to the token or errors(if encountered).
68 | - alternative approach to `execute()` in combination with the `onChange()` prop - [example below](#invisible-recaptcha)
69 |
70 | Example:
71 | ```javascript
72 | const recaptchaRef = React.createRef();
73 | ...
74 | onSubmit = () => {
75 | const recaptchaValue = recaptchaRef.current.getValue();
76 | this.props.onSubmit(recaptchaValue);
77 | }
78 | render() {
79 | return (
80 |
87 | )
88 | }
89 | ```
90 |
91 | ### Invisible reCAPTCHA
92 |
93 | [▶ Codesandbox invisible example](https://codesandbox.io/s/gifted-cache-10q74jj593)
94 |
95 | See the [reCAPTCHA documentation](https://developers.google.com/recaptcha/docs/invisible) to see how to configure it.
96 |
97 | With the invisible option, you need to handle things a bit differently. You will need to call the `execute` method yourself.
98 |
99 | ```jsx
100 | import ReCAPTCHA from "react-google-recaptcha";
101 |
102 | const recaptchaRef = React.createRef();
103 |
104 | ReactDOM.render(
105 | ,
113 | document.body
114 | );
115 | ```
116 |
117 | Additionally, you can use the `executeAsync` method to use a promise based approach.
118 |
119 | ```jsx
120 | import ReCAPTCHA from "react-google-recaptcha";
121 |
122 |
123 | const ReCAPTCHAForm = (props) => {
124 | const recaptchaRef = React.useRef();
125 |
126 | const onSubmitWithReCAPTCHA = async () => {
127 | const token = await recaptchaRef.current.executeAsync();
128 |
129 | // apply to form data
130 | }
131 |
132 | return (
133 |
140 | )
141 |
142 | }
143 |
144 | ReactDOM.render(
145 | ,
146 | document.body
147 | );
148 | ```
149 |
150 |
151 | ### Advanced usage
152 |
153 | #### Global properties used by reCaptcha
154 |
155 | __useRecaptchaNet__: If google.com is blocked, you can set `useRecaptchaNet` to `true` so that the component uses recaptcha.net instead.
156 |
157 | __enterprise__: if you want to use Google Enterprise Recaptcha, instead of the free version, set `enterprise` to `true`.
158 |
159 | Example global properties:
160 | ```js
161 | window.recaptchaOptions = {
162 | useRecaptchaNet: true,
163 | enterprise: true,
164 | };
165 | ```
166 |
167 | ### CSP Nonce support
168 | ```js
169 | window.recaptchaOptions = {
170 | nonce: document.querySelector('meta[name=\'csp-nonce\']').getAttribute('content'),
171 | };
172 | ```
173 |
174 | #### ReCaptcha loading google recaptcha script manually
175 |
176 | You can also use the barebone components doing the following. Using that component will oblige you to manage the grecaptcha dep and load the script by yourself.
177 |
178 | ```jsx
179 | import { ReCAPTCHA } from "react-google-recaptcha";
180 |
181 | const grecaptchaObject = window.grecaptcha // You must provide access to the google grecaptcha object.
182 |
183 | render(
184 | this.recaptcha = r}
186 | sitekey="Your client site key"
187 | grecaptcha={grecaptchaObject}
188 | />,
189 | document.body
190 | );
191 | ```
192 |
193 | #### Hiding the Recaptcha
194 |
195 | According to the [google docs](https://developers.google.com/recaptcha/docs/faq#id-like-to-hide-the-recaptcha-badge.-what-is-allowed) you are allowed to hide the badge as long as you include the reCAPTCHA branding visibly in the user flow. Please include the following text:
196 |
197 | ```
198 | This site is protected by reCAPTCHA and the Google
199 | Privacy Policy and
200 | Terms of Service apply.
201 | ```
202 |
203 | If you wish to hide the badge you must add:
204 |
205 | ```
206 | .grecaptcha-badge { visibility: hidden; }
207 |
208 | ```
209 |
210 | to your css.
211 |
212 |
213 |
214 | [ci.img]: https://github.com/dozoisch/react-google-recaptcha/actions/workflows/standard-ci.yml/badge.svg?branch=master
215 | [ci.url]: https://github.com/dozoisch/react-google-recaptcha/actions/workflows/standard-ci.yml
216 | [npm.img]: https://badge.fury.io/js/react-google-recaptcha.svg
217 | [npm.url]: http://badge.fury.io/js/react-google-recaptcha
218 | [npm.dl.img]: https://img.shields.io/npm/dm/react-google-recaptcha.svg
219 | [npm.dl.url]: https://www.npmjs.com/package/react-google-recaptcha
220 |
221 | [reCAPTCHA]: https://developers.google.com/recaptcha/docs/display
222 | [signup]: http://www.google.com/recaptcha/admin
223 | [docs]: https://developers.google.com/recaptcha
224 | [docs_theme]: https://developers.google.com/recaptcha/docs/faq#can-i-customize-the-recaptcha-widget
225 | [js_api]: https://developers.google.com/recaptcha/docs/display#js_api
226 | [rb]: https://github.com/react-bootstrap/react-bootstrap/
227 | [reCAPTCHA secure-token]: https://developers.google.com/recaptcha/docs/secure_token
228 | [reCAPTCHA hl]: https://developers.google.com/recaptcha/docs/language
229 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | "use strict";
3 | // For a detailed explanation regarding each configuration property, visit:
4 | // https://jestjs.io/docs/en/configuration.html
5 |
6 | module.exports = {
7 | // All imported modules in your tests should be mocked automatically
8 | // automock: false,
9 |
10 | // Stop running tests after the first failure
11 | // bail: false,
12 |
13 | // Respect "browser" field in package.json when resolving modules
14 | // browser: false,
15 |
16 | // The directory where Jest should store its cached dependency information
17 | // cacheDirectory: "/var/folders/l7/cx7wgg653n7619h3gf_zjjzr0000gn/T/jest_dx",
18 |
19 | // Automatically clear mock calls and instances between every test
20 | clearMocks: true,
21 |
22 | // Indicates whether the coverage information should be collected while executing the test
23 | // collectCoverage: false,
24 |
25 | // An array of glob patterns indicating a set of files for which coverage information should be collected
26 | // collectCoverageFrom: null,
27 |
28 | // The directory where Jest should output its coverage files
29 | // coverageDirectory: null,
30 |
31 | // An array of regexp pattern strings used to skip coverage collection
32 | // coveragePathIgnorePatterns: [
33 | // "/node_modules/"
34 | // ],
35 |
36 | // A list of reporter names that Jest uses when writing coverage reports
37 | // coverageReporters: [
38 | // "json",
39 | // "text",
40 | // "lcov",
41 | // "clover"
42 | // ],
43 |
44 | // An object that configures minimum threshold enforcement for coverage results
45 | // coverageThreshold: null,
46 |
47 | // Make calling deprecated APIs throw helpful error messages
48 | // errorOnDeprecated: false,
49 |
50 | // Force coverage collection from ignored files usin a array of glob patterns
51 | // forceCoverageMatch: [],
52 |
53 | // A path to a module which exports an async function that is triggered once before all test suites
54 | // globalSetup: null,
55 |
56 | // A path to a module which exports an async function that is triggered once after all test suites
57 | // globalTeardown: null,
58 |
59 | // A set of global variables that need to be available in all test environments
60 | // globals: {},
61 |
62 | // An array of directory names to be searched recursively up from the requiring module's location
63 | // moduleDirectories: [
64 | // "node_modules"
65 | // ],
66 |
67 | // An array of file extensions your modules use
68 | // moduleFileExtensions: [
69 | // "js",
70 | // "json",
71 | // "jsx",
72 | // "node"
73 | // ],
74 |
75 | // A map from regular expressions to module names that allow to stub out resources with a single module
76 | // moduleNameMapper: {},
77 |
78 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
79 | // modulePathIgnorePatterns: [],
80 |
81 | // Activates notifications for test results
82 | // notify: false,
83 |
84 | // An enum that specifies notification mode. Requires { notify: true }
85 | // notifyMode: "always",
86 |
87 | // A preset that is used as a base for Jest's configuration
88 | // preset: null,
89 |
90 | // Run tests from one or more projects
91 | // projects: null,
92 |
93 | // Use this configuration option to add custom reporters to Jest
94 | // reporters: undefined,
95 |
96 | // Automatically reset mock state between every test
97 | // resetMocks: false,
98 |
99 | // Reset the module registry before running each individual test
100 | // resetModules: false,
101 |
102 | // A path to a custom resolver
103 | // resolver: null,
104 |
105 | // Automatically restore mock state between every test
106 | // restoreMocks: false,
107 |
108 | // The root directory that Jest should scan for tests and modules within
109 | // rootDir: null,
110 |
111 | // A list of paths to directories that Jest should use to search for files in
112 | // roots: [
113 | // ""
114 | // ],
115 |
116 | // Allows you to use a custom runner instead of Jest's default test runner
117 | // runner: "jest-runner",
118 |
119 | // The paths to modules that run some code to configure or set up the testing environment before each test
120 | // setupFiles: [],
121 |
122 | // The path to a module that runs some code to configure or set up the testing framework before each test
123 | // setupTestFrameworkScriptFile: null,
124 |
125 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
126 | // snapshotSerializers: [],
127 |
128 | // The test environment that will be used for testing
129 | testEnvironment: "jsdom",
130 |
131 | // Options that will be passed to the testEnvironment
132 | // testEnvironmentOptions: {},
133 |
134 | // Adds a location field to test results
135 | // testLocationInResults: false,
136 |
137 | // The glob patterns Jest uses to detect test files
138 | testMatch: ["**/?(*.)+(spec|test).js?(x)"],
139 |
140 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
141 | testPathIgnorePatterns: ["/node_modules/", "webpack.config.test.js"],
142 |
143 | // The regexp pattern Jest uses to detect test files
144 | // testRegex: "",
145 |
146 | // This option allows the use of a custom results processor
147 | // testResultsProcessor: null,
148 |
149 | // This option allows use of a custom test runner
150 | // testRunner: "jasmine2",
151 |
152 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
153 | // testURL: "http://localhost",
154 |
155 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
156 | // timers: "real",
157 |
158 | // A map from regular expressions to paths to transformers
159 | // transform: null,
160 |
161 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
162 | // transformIgnorePatterns: [
163 | // "/node_modules/"
164 | // ],
165 |
166 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
167 | // unmockedModulePathPatterns: undefined,
168 |
169 | // Indicates whether each individual test should be reported during the run
170 | // verbose: null,
171 |
172 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
173 | // watchPathIgnorePatterns: [],
174 |
175 | // Whether to use watchman for file crawling
176 | // watchman: true,
177 | };
178 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-google-recaptcha",
3 | "version": "3.1.0",
4 | "description": "React Component Wrapper for Google reCAPTCHA",
5 | "main": "lib/index.js",
6 | "module": "lib/esm/index.js",
7 | "directories": {
8 | "lib": "lib/"
9 | },
10 | "scripts": {
11 | "build": "rm -rf lib && npm run build:cjs && npm run build:esm",
12 | "build:cjs": "babel src --out-dir lib",
13 | "build:esm": "cross-env BABEL_ENV=esm babel src --out-dir lib/esm",
14 | "prepare": "npm run build",
15 | "pretty": "prettier --write src/*.js ./*.js test/*.js",
16 | "lint": "eslint ./",
17 | "lint:fix": "eslint ./ --fix",
18 | "test": "cross-env BABEL_ENV=development jest"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/dozoisch/react-google-recaptcha.git"
23 | },
24 | "keywords": [
25 | "react",
26 | "react-component",
27 | "captcha",
28 | "recaptcha",
29 | "google-recaptcha"
30 | ],
31 | "author": "Hugo Dozois ",
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/dozoisch/react-google-recaptcha/issues"
35 | },
36 | "homepage": "https://github.com/dozoisch/react-google-recaptcha",
37 | "peerDependencies": {
38 | "react": ">=16.4.1"
39 | },
40 | "devDependencies": {
41 | "@babel/cli": "^7.19.3",
42 | "@babel/core": "^7.19.6",
43 | "@babel/preset-env": "^7.19.4",
44 | "@babel/preset-react": "^7.18.6",
45 | "@testing-library/react": "^13.4.0",
46 | "cross-env": "^7.0.3",
47 | "eslint": "^8.26.0",
48 | "eslint-config-prettier": "^8.5.0",
49 | "eslint-plugin-prettier": "^4.2.1",
50 | "eslint-plugin-react": "^7.31.10",
51 | "jest": "^29.2.2",
52 | "jest-environment-jsdom": "^29.2.2",
53 | "prettier": "^2.7.1",
54 | "react": "^18.2.0",
55 | "react-dom": "^18.2.0"
56 | },
57 | "dependencies": {
58 | "prop-types": "^15.5.0",
59 | "react-async-script": "^1.2.0"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import RecaptchaWrapper from "./recaptcha-wrapper";
2 | import ReCAPTCHA from "./recaptcha";
3 |
4 | export default RecaptchaWrapper;
5 | export { ReCAPTCHA };
6 |
--------------------------------------------------------------------------------
/src/recaptcha-wrapper.js:
--------------------------------------------------------------------------------
1 | import ReCAPTCHA from "./recaptcha";
2 | import makeAsyncScriptLoader from "react-async-script";
3 |
4 | const callbackName = "onloadcallback";
5 | const globalName = "grecaptcha";
6 |
7 | function getOptions() {
8 | return (typeof window !== "undefined" && window.recaptchaOptions) || {};
9 | }
10 |
11 | function getURL() {
12 | const dynamicOptions = getOptions();
13 | const hostname = dynamicOptions.useRecaptchaNet ? "recaptcha.net" : "www.google.com";
14 | if (dynamicOptions.enterprise) {
15 | return `https://${hostname}/recaptcha/enterprise.js?onload=${callbackName}&render=explicit`;
16 | }
17 | return `https://${hostname}/recaptcha/api.js?onload=${callbackName}&render=explicit`;
18 | }
19 |
20 | export default makeAsyncScriptLoader(getURL, {
21 | callbackName,
22 | globalName,
23 | attributes: getOptions().nonce ? { nonce: getOptions().nonce } : {},
24 | })(ReCAPTCHA);
25 |
--------------------------------------------------------------------------------
/src/recaptcha.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export default class ReCAPTCHA extends React.Component {
5 | constructor() {
6 | super();
7 | this.handleExpired = this.handleExpired.bind(this);
8 | this.handleErrored = this.handleErrored.bind(this);
9 | this.handleChange = this.handleChange.bind(this);
10 | this.handleRecaptchaRef = this.handleRecaptchaRef.bind(this);
11 | }
12 |
13 | getCaptchaFunction(fnName) {
14 | if (this.props.grecaptcha) {
15 | if (this.props.grecaptcha.enterprise) {
16 | return this.props.grecaptcha.enterprise[fnName];
17 | }
18 | return this.props.grecaptcha[fnName];
19 | }
20 | return null;
21 | }
22 |
23 | getValue() {
24 | const getResponse = this.getCaptchaFunction("getResponse");
25 | if (getResponse && this._widgetId !== undefined) {
26 | return getResponse(this._widgetId);
27 | }
28 | return null;
29 | }
30 |
31 | getWidgetId() {
32 | if (this.props.grecaptcha && this._widgetId !== undefined) {
33 | return this._widgetId;
34 | }
35 | return null;
36 | }
37 |
38 | execute() {
39 | const execute = this.getCaptchaFunction("execute");
40 | if (execute && this._widgetId !== undefined) {
41 | return execute(this._widgetId);
42 | } else {
43 | this._executeRequested = true;
44 | }
45 | }
46 |
47 | executeAsync() {
48 | return new Promise((resolve, reject) => {
49 | this.executionResolve = resolve;
50 | this.executionReject = reject;
51 | this.execute();
52 | });
53 | }
54 |
55 | reset() {
56 | const resetter = this.getCaptchaFunction("reset");
57 | if (resetter && this._widgetId !== undefined) {
58 | resetter(this._widgetId);
59 | }
60 | }
61 |
62 | forceReset() {
63 | const resetter = this.getCaptchaFunction("reset");
64 | if (resetter) {
65 | resetter();
66 | }
67 | }
68 |
69 | handleExpired() {
70 | if (this.props.onExpired) {
71 | this.props.onExpired();
72 | } else {
73 | this.handleChange(null);
74 | }
75 | }
76 |
77 | handleErrored() {
78 | if (this.props.onErrored) {
79 | this.props.onErrored();
80 | }
81 | if (this.executionReject) {
82 | this.executionReject();
83 | delete this.executionResolve;
84 | delete this.executionReject;
85 | }
86 | }
87 |
88 | handleChange(token) {
89 | if (this.props.onChange) {
90 | this.props.onChange(token);
91 | }
92 | if (this.executionResolve) {
93 | this.executionResolve(token);
94 | delete this.executionReject;
95 | delete this.executionResolve;
96 | }
97 | }
98 |
99 | explicitRender() {
100 | const render = this.getCaptchaFunction("render");
101 | if (render && this._widgetId === undefined) {
102 | const wrapper = document.createElement("div");
103 | this._widgetId = render(wrapper, {
104 | sitekey: this.props.sitekey,
105 | callback: this.handleChange,
106 | theme: this.props.theme,
107 | type: this.props.type,
108 | tabindex: this.props.tabindex,
109 | "expired-callback": this.handleExpired,
110 | "error-callback": this.handleErrored,
111 | size: this.props.size,
112 | stoken: this.props.stoken,
113 | hl: this.props.hl,
114 | badge: this.props.badge,
115 | isolated: this.props.isolated,
116 | });
117 | this.captcha.appendChild(wrapper);
118 | }
119 | if (this._executeRequested && this.props.grecaptcha && this._widgetId !== undefined) {
120 | this._executeRequested = false;
121 | this.execute();
122 | }
123 | }
124 |
125 | componentDidMount() {
126 | this.explicitRender();
127 | }
128 |
129 | componentDidUpdate() {
130 | this.explicitRender();
131 | }
132 |
133 | handleRecaptchaRef(elem) {
134 | this.captcha = elem;
135 | }
136 |
137 | render() {
138 | // consume properties owned by the reCATPCHA, pass the rest to the div so the user can style it.
139 | /* eslint-disable no-unused-vars */
140 | const {
141 | sitekey,
142 | onChange,
143 | theme,
144 | type,
145 | tabindex,
146 | onExpired,
147 | onErrored,
148 | size,
149 | stoken,
150 | grecaptcha,
151 | badge,
152 | hl,
153 | isolated,
154 | ...childProps
155 | } = this.props;
156 | /* eslint-enable no-unused-vars */
157 | return ;
158 | }
159 | }
160 |
161 | ReCAPTCHA.displayName = "ReCAPTCHA";
162 | ReCAPTCHA.propTypes = {
163 | sitekey: PropTypes.string.isRequired,
164 | onChange: PropTypes.func,
165 | grecaptcha: PropTypes.object,
166 | theme: PropTypes.oneOf(["dark", "light"]),
167 | type: PropTypes.oneOf(["image", "audio"]),
168 | tabindex: PropTypes.number,
169 | onExpired: PropTypes.func,
170 | onErrored: PropTypes.func,
171 | size: PropTypes.oneOf(["compact", "normal", "invisible"]),
172 | stoken: PropTypes.string,
173 | hl: PropTypes.string,
174 | badge: PropTypes.oneOf(["bottomright", "bottomleft", "inline"]),
175 | isolated: PropTypes.bool,
176 | };
177 | ReCAPTCHA.defaultProps = {
178 | onChange: () => {},
179 | theme: "light",
180 | type: "image",
181 | tabindex: 0,
182 | size: "normal",
183 | badge: "bottomright",
184 | };
185 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true,
4 | "node": true
5 | },
6 | "rules": {
7 | "no-script-url": 1,
8 | "no-unused-expressions": 0,
9 | "react/no-multi-comp": 0
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import "es5-shim";
2 | import "es6-shim";
3 | const testsContext = require.context(".", true, /-spec$/);
4 | testsContext.keys().forEach(testsContext);
5 |
--------------------------------------------------------------------------------
/test/recaptcha-wrapper.spec.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "@testing-library/react";
3 | import ReCAPTCHA from "../src/recaptcha-wrapper";
4 |
5 | const VALUE = "some value";
6 | const WIDGET_ID = "someWidgetId";
7 |
8 | const grecaptchaMock = {
9 | render(node, options) {
10 | expect(node).toBeTruthy();
11 | expect(options).toBeTruthy();
12 | return WIDGET_ID;
13 | },
14 |
15 | getResponse(widgetId) {
16 | expect(widgetId).toBe(WIDGET_ID);
17 | return VALUE;
18 | },
19 | };
20 |
21 | describe("ReCAPTCHAWrapper", () => {
22 | beforeEach(() => {
23 | window.grecaptcha = grecaptchaMock;
24 | });
25 | afterEach(() => {
26 | delete window.grecaptcha;
27 | });
28 | it("should be wrapped properly", () => {
29 | expect(ReCAPTCHA.displayName).toBe("AsyncScriptLoader(ReCAPTCHA)");
30 | });
31 | it("should proxy functions", () => {
32 | const ReCaptchaRef = React.createRef();
33 |
34 | render();
35 | expect(ReCaptchaRef.current.getValue()).toBe(VALUE);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/recaptcha.spec.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "@testing-library/react";
3 |
4 | import ReCAPTCHA from "../src/recaptcha"; // eslint-disable-line no-unused-vars
5 |
6 | describe("ReCAPTCHA", () => {
7 | it("Rendered Component should be a div", () => {
8 | const { container } = render();
9 | expect(container.firstChild.nodeName).toBe("DIV");
10 | });
11 | it("Rendered Component should contained passed props", () => {
12 | const props = {
13 | className: "TheClassName",
14 | id: "superdefinedId",
15 | onChange: jest.fn(),
16 | };
17 | const { container } = render();
18 | expect(container.firstChild.id).toBe(props.id);
19 | expect(container.firstChild.className).toBe(props.className);
20 | });
21 |
22 | it("should call grecaptcha.render, when it is already loaded", () => {
23 | return new Promise((resolve) => {
24 | const grecaptchaMock = {
25 | render(node, options) {
26 | expect(node).toBeTruthy();
27 | expect(options.sitekey).toBe("xxx");
28 | resolve();
29 | },
30 | };
31 | render();
32 | });
33 | });
34 | it("reset, should call grecaptcha.reset with the widget id", () => {
35 | const WIDGET_ID = "someWidgetId";
36 | const grecaptchaMock = {
37 | render() {
38 | return WIDGET_ID;
39 | },
40 | reset: jest.fn(),
41 | };
42 | const ReCaptchaRef = React.createRef();
43 | render(
44 | ,
50 | );
51 | ReCaptchaRef.current.reset();
52 | expect(grecaptchaMock.reset).toBeCalledWith(WIDGET_ID);
53 | });
54 | it("execute, should call grecaptcha.execute with the widget id", () => {
55 | const WIDGET_ID = "someWidgetId";
56 | const grecaptchaMock = {
57 | render() {
58 | return WIDGET_ID;
59 | },
60 | execute: jest.fn(),
61 | };
62 | // wrapping component example that applies a ref to ReCAPTCHA
63 | class WrappingComponent extends React.Component {
64 | constructor(props) {
65 | super(props);
66 | this._internalRef = React.createRef();
67 | }
68 | render() {
69 | return (
70 |
71 |
78 |
79 | );
80 | }
81 | }
82 | const wrappingRef = React.createRef();
83 | render();
84 | wrappingRef.current._internalRef.current.execute();
85 | expect(grecaptchaMock.execute).toBeCalledWith(WIDGET_ID);
86 | });
87 | it("executeAsync, should call grecaptcha.execute with the widget id", () => {
88 | const WIDGET_ID = "someWidgetId";
89 | const grecaptchaMock = {
90 | render() {
91 | return WIDGET_ID;
92 | },
93 | execute: jest.fn(),
94 | };
95 | // wrapping component example that applies a ref to ReCAPTCHA
96 | class WrappingComponent extends React.Component {
97 | constructor(props) {
98 | super(props);
99 | this._internalRef = React.createRef();
100 | }
101 | render() {
102 | return (
103 |
104 |
111 |
112 | );
113 | }
114 | }
115 | const wrappingRef = React.createRef();
116 | render();
117 | wrappingRef.current._internalRef.current.executeAsync();
118 | expect(grecaptchaMock.execute).toBeCalledWith(WIDGET_ID);
119 | });
120 | it("executeAsync, should return a promise that resolves with the token", () => {
121 | const WIDGET_ID = "someWidgetId";
122 | const TOKEN = "someToken";
123 | const grecaptchaMock = (() => {
124 | let _callback;
125 | return {
126 | render(_, { callback }) {
127 | _callback = callback;
128 | return WIDGET_ID;
129 | },
130 | execute() {
131 | _callback(TOKEN);
132 | },
133 | };
134 | })();
135 | // wrapping component example that applies a ref to ReCAPTCHA
136 | class WrappingComponent extends React.Component {
137 | constructor(props) {
138 | super(props);
139 | this._internalRef = React.createRef();
140 | }
141 | render() {
142 | return (
143 |
144 |
151 |
152 | );
153 | }
154 | }
155 |
156 | const wrappingRef = React.createRef();
157 | render();
158 | const executeAsyncDirectValue = wrappingRef.current._internalRef.current.executeAsync();
159 | expect(executeAsyncDirectValue).toBeInstanceOf(Promise);
160 | return executeAsyncDirectValue.then((executeAsyncResolveValue) => {
161 | expect(executeAsyncResolveValue).toBe(TOKEN);
162 | });
163 | });
164 | describe("Expired", () => {
165 | it("should call onChange with null when response is expired", () => {
166 | const WIDGET_ID = "someWidgetId";
167 | const onChange = jest.fn();
168 | const grecaptchaMock = {
169 | render() {
170 | return WIDGET_ID;
171 | },
172 | };
173 | const ReCaptchaRef = React.createRef();
174 | render(
175 | ,
181 | );
182 | ReCaptchaRef.current.handleExpired();
183 | expect(onChange).toBeCalledWith(null);
184 | });
185 | it("should call onExpired when response is expired", () => {
186 | const WIDGET_ID = "someWidgetId";
187 | const onChange = jest.fn();
188 | const onExpired = jest.fn();
189 | const grecaptchaMock = {
190 | render() {
191 | return WIDGET_ID;
192 | },
193 | };
194 | const ReCaptchaRef = React.createRef();
195 | render(
196 | ,
203 | );
204 | ReCaptchaRef.current.handleExpired();
205 | expect(onChange).not.toHaveBeenCalled();
206 | expect(onExpired).toHaveBeenCalled();
207 | });
208 | });
209 | describe("Errored", () => {
210 | it("should call onErrored when grecaptcha errored", () => {
211 | const WIDGET_ID = "someWidgetId";
212 | const onErrored = jest.fn();
213 | const grecaptchaMock = {
214 | render() {
215 | return WIDGET_ID;
216 | },
217 | };
218 | const ReCaptchaRef = React.createRef();
219 | render(
220 | ,
227 | );
228 | ReCaptchaRef.current.handleErrored();
229 | expect(onErrored).toHaveBeenCalled();
230 | });
231 | });
232 | });
233 |
--------------------------------------------------------------------------------