├── .babelrc
├── .gitattributes
├── .github
├── CODEOWNERS
└── workflows
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── demo
├── app
│ ├── App.jsx
│ ├── components
│ │ ├── CustomFrame.jsx
│ │ └── index.js
│ └── examples
│ │ ├── AsyncExample.jsx
│ │ ├── ClassExample.jsx
│ │ ├── FrameExample.jsx
│ │ └── index.js
├── frame.html
├── index.html
├── index.js
└── webpack.config.mjs
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── index.js
└── utils.js
├── tests
├── hcaptcha.mock.js
├── hcaptcha.spec.js
└── utils.test.js
└── types
└── index.d.ts
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "sourceType": "unambiguous",
3 | "presets": [
4 | "@babel/preset-env",
5 | "@babel/preset-react"
6 | ],
7 | "plugins": [
8 | "add-module-exports",
9 | ["@babel/plugin-transform-runtime", {
10 | "regenerator": true
11 | }]
12 | ],
13 | "env": {
14 | "esm": {
15 | "sourceType": "unambiguous",
16 | "presets": [
17 | [
18 | "@babel/preset-env",
19 | {
20 | "loose": true,
21 | "modules": false
22 | }
23 | ],
24 | "@babel/preset-react"
25 | ],
26 | "plugins": [["@babel/plugin-transform-runtime", {
27 | "regenerator": true
28 | }]]
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence,they will
3 | # be requested for review when someone opens a pull request.
4 | * @hCaptcha/react-reviewers
5 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | name: Build & Test
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Checkout code
9 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
10 | - uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
11 | with:
12 | node-version: '18.x'
13 | - run: npm ci
14 | - run: npm run transpile
15 | - run: npm run test
16 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 | schedule:
15 | - cron: '0 20 * * 3'
16 |
17 | jobs:
18 | analyze:
19 | name: Analyze
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | # Override automatic language detection by changing the below list
26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
27 | language: ['javascript']
28 | # Learn more...
29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34 | with:
35 | # We must fetch at least the immediate parents so that if this is
36 | # a pull request then we can checkout the head.
37 | fetch-depth: 2
38 |
39 | - uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
40 | with:
41 | node-version: '18.x'
42 |
43 | # If this run was triggered by a pull request event, then checkout
44 | # the head of the pull request instead of the merge commit.
45 | - run: git checkout HEAD^2
46 | if: ${{ github.event_name == 'pull_request' }}
47 |
48 | # Initializes the CodeQL tools for scanning.
49 | - name: Initialize CodeQL
50 | uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.13
51 | with:
52 | languages: ${{ matrix.language }}
53 | # If you wish to specify custom queries, you can do so here or in a config file.
54 | # By default, queries listed here will override any specified in a config file.
55 | # Prefix the list here with "+" to use these queries and those in the config file.
56 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
57 |
58 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
59 | # If this step fails, then you should remove it and run the build manually (see below)
60 | - name: Autobuild
61 | uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.13
62 |
63 | # ℹ️ Command-line programs to run using the OS shell.
64 | # 📚 https://git.io/JvXDl
65 |
66 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
67 | # and modify them (or add more) to build your code if your project
68 | # uses a compiled language
69 |
70 | #- run: |
71 | # make bootstrap
72 | # make release
73 |
74 | - name: Perform CodeQL Analysis
75 | uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.13
76 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish NPM
2 | on:
3 | workflow_dispatch:
4 | release:
5 | types: [ created ]
6 | jobs:
7 | build:
8 | name: Build & Test & Publish
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
13 | - uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
14 | with:
15 | node-version: '18.x'
16 | registry-url: 'https://registry.npmjs.org'
17 | - run: npm ci
18 | - run: npm run transpile
19 | - run: npm run test
20 | - run: npm publish
21 | env:
22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | *.code-workspace
4 | *.sublime-workspace
5 | .vscode/
6 | .idea/
7 |
8 | .DS_Store
9 |
10 | dist
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 hCaptcha
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React hCaptcha Component Library
2 |
3 |
4 | hCaptcha Component Library for ReactJS.
5 |
6 | [hCaptcha](https://www.hcaptcha.com) is a drop-replacement for reCAPTCHA that protects user privacy.
7 |
8 | Sign up at [hCaptcha](https://www.hcaptcha.com) to get your sitekey today. **You need a sitekey to use this library.**
9 |
10 | *Also compatible with Preact.*
11 |
12 | 1. [Installation](#installation)
13 | 2. [References](#references)
14 | 3. [Debugging](#debugging)
15 | 4. [Contributing](#contributing)
16 |
17 | ## Installation
18 |
19 | You can install this library via npm with:
20 |
21 | ```
22 | npm install @hcaptcha/react-hcaptcha --save
23 | ```
24 |
25 | ### Implementation
26 | The two requirements for usage are the `sitekey` [prop](#props) and a `parent component` such as a `
`. The component will automatically include and load the
27 | hCaptcha API library and append it to the parent component. This is designed for ease of use with the hCaptcha API!
28 |
29 | #### Standard
30 |
31 | ```js
32 | import HCaptcha from '@hcaptcha/react-hcaptcha';
33 |
34 |
35 | handleVerificationSuccess(token, ekey)}
38 | />
39 |
40 | ```
41 |
42 | #### Programmatic
43 | In the event you want to call the hCaptcha client API directly, you can do so by using the hook `useRef` and waiting for `onLoad` to be called. By waiting for `onLoad` the hCaptcha API will be ready and the hCaptcha client will have been setup. See the following example:
44 |
45 | ```js
46 | import { useEffect, useRef, useState } from "react";
47 | import HCaptcha from "@hcaptcha/react-hcaptcha";
48 |
49 | export default function Form() {
50 | const [token, setToken] = useState(null);
51 | const captchaRef = useRef(null);
52 |
53 | const onLoad = () => {
54 | // this reaches out to the hCaptcha JS API and runs the
55 | // execute function on it. you can use other functions as
56 | // documented here:
57 | // https://docs.hcaptcha.com/configuration#jsapi
58 | captchaRef.current.execute();
59 | };
60 |
61 | useEffect(() => {
62 |
63 | if (token)
64 | console.log(`hCaptcha Token: ${token}`);
65 |
66 | }, [token]);
67 |
68 | return (
69 |
77 | );
78 | }
79 | ```
80 |
81 | **Typescript Support** \
82 | If you want to reassign the component name, you could consider making a util that imports the component, then re-exports it as a default.
83 |
84 | ```ts
85 | // utils/captcha.ts
86 | import HCaptcha from '@hcaptcha/react-hcaptcha';
87 | export default HCaptcha;
88 |
89 | // MyFormComponent.tsx
90 | import { default as RenamedCaptcha } from '../utils/captcha';
91 |
92 |
93 |
94 | ```
95 |
96 | #### Advanced
97 |
98 | In most real-world implementations, you'll probably be using a form library such as [Formik](https://github.com/jaredpalmer/formik) or [React Hook Form](https://github.com/react-hook-form/react-hook-form).
99 |
100 | In these instances, you'll most likely want to use `ref` to handle the callbacks as well as handle field-level validation of a `captcha` field. For an example of this, you can view this [CodeSandbox](https://codesandbox.io/s/react-hcaptchaform-example-forked-ngxge?file=/src/Form.jsx). This `ref` will point to an instance of the [hCaptcha API](https://docs.hcaptcha.com/configuration#jsapi) where can you interact directly with it.
101 |
102 | #### Passing in fields like `rqdata` to `execute()`
103 |
104 | Passing an object into the `execute(yourObj)` call will send it through to the underlying JS API. This enables support for Enterprise features like `rqdata`. A simple example is below:
105 |
106 | ```
107 | const {sitekey, rqdata} = props;
108 | const captchaRef = React.useRef(null);
109 |
110 | const onLoad = () => {
111 | const executePayload = {};
112 | if (rqdata) {
113 | executePayload['rqdata'] = rqdata;
114 | }
115 | captchaRef.current?.execute(executePayload);
116 | };
117 |
118 | return ;
119 | ```
120 |
121 | ### References
122 |
123 | #### Props
124 |
125 | |Name|Values/Type|Required|Default|Description|
126 | |---|---|---|---|---|
127 | |`sitekey`|String|**Yes**|`-`|This is your sitekey, this allows you to load captcha. If you need a sitekey, please visit [hCaptcha](https://www.hcaptcha.com), and sign up to get your sitekey.|
128 | |`size`|String (normal, compact, invisible)|No|`normal`|This specifies the "size" of the component. hCaptcha allows you to decide how big the component will appear on render, this always defaults to normal.|
129 | |`theme`|String (light, dark, contrast) or Object|No|`light`|hCaptcha supports both a light and dark theme. Defaults to light. Takes Object if custom theme is used.|
130 | |`tabindex`|Integer|No|`0`|Set the tabindex of the widget and popup. When appropriate, this can make navigation of your site more intuitive.|
131 | |`languageOverride`|String (ISO 639-2 code)|No|`auto`|hCaptcha auto-detects language via the user's browser. This overrides that to set a default UI language. See [language codes](https://hcaptcha.com/docs/languages).|
132 | |`reCaptchaCompat`|Boolean|No|`true`|Disable drop-in replacement for reCAPTCHA with `false` to prevent hCaptcha from injecting into `window.grecaptcha`.|
133 | |`id`|String|No|`random id`|Manually set the ID of the hCaptcha component. Make sure each hCaptcha component generated on a single page has its own unique ID when using this prop.|
134 | |`apihost`|String|No|`-`|See enterprise docs.|
135 | |`assethost`|String|No|`-`|See enterprise docs.|
136 | |`endpoint`|String|No|`-`|See enterprise docs.|
137 | |`host`|String|No|`-`|See enterprise docs.|
138 | |`imghost`|String|No|`-`|See enterprise docs.|
139 | |`reportapi`|String|No|`-`|See enterprise docs.|
140 | |`sentry`|String|No|`-`|See enterprise docs.|
141 | |`secureApi`|Boolean|No|`-`|See enterprise docs.|
142 | |`scriptSource`|String|No|`-`|See enterprise docs.|
143 | | `cleanup` | Boolean | No | `true` | Remove script tag after setup.|
144 | |`custom`|Boolean|No|`-`|Custom theme: see enterprise docs.|
145 | |`loadAsync`|Boolean|No|`true`|Set if the script should be loaded asynchronously.|
146 | |`scriptLocation`|Element|No|`document.head`| Location of where to append the script tag. Make sure to add it to an area that will persist to prevent loading multiple times in the same document view. Note: If `null` is provided, the `document.head` will be used.|
147 |
148 | #### Events
149 |
150 | |Event|Params|Description|
151 | |---|---|---|
152 | |`onError`|`err`|When an error occurs. Component will reset immediately after an error.|
153 | |`onVerify`|`token, eKey`|When challenge is completed. The response `token` and an `eKey` (session id) are passed along.|
154 | |`onExpire`|-|When the current token expires.|
155 | |`onLoad`|-|When the hCaptcha API loads.|
156 | |`onOpen`|-|When the user display of a challenge starts.|
157 | |`onClose`|-|When the user dismisses a challenge.|
158 | |`onChalExpired`|-|When the user display of a challenge times out with no answer.|
159 |
160 | #### Methods
161 |
162 | |Method|Description|
163 | |---|---|
164 | |`execute()`|Programmatically trigger a challenge request. Additionally, this method can be run asynchronously and returns a promise with the `token` and `eKey` when the challenge is completed.|
165 | |`getRespKey()`|Get the current challenge reference ID|
166 | |`getResponse()`|Get the current challenge response token from completed challenge|
167 | |`resetCaptcha()`|Reset the current challenge|
168 | |`setData()`|See enterprise docs.|
169 |
170 |
171 | > **Note** \
172 | > Make sure to reset the hCaptcha state when you submit your form by calling the method `.resetCaptcha` on your hCaptcha React Component! Passcodes are one-time use, so if your user submits the same passcode twice then it will be rejected by the server the second time.
173 |
174 | Please refer to the demo for examples of basic usage and an invisible hCaptcha.
175 |
176 | Alternatively, see [this sandbox code](https://codesandbox.io/s/react-hcaptchaform-example-invisible-f7ekt) for a quick form example of invisible hCaptcha on a form submit button.
177 |
178 | Please note that "invisible" simply means that no hCaptcha button will be rendered. Whether a challenge shows up will depend on the sitekey difficulty level. Note to hCaptcha Enterprise ([BotStop](https://www.botstop.com)) users: select "Passive" or "99.9% Passive" modes to get this No-CAPTCHA behavior.
179 |
180 |
181 |
182 |
183 | ### Debugging
184 |
185 | 1. #### Invalid hCaptcha Id:
186 | This issue generally occurs when the component is re-rendered causing the current `useRef` to become stale, meaning the `ref` being used is no longer available in the DOM.
187 |
188 |
189 | 2. #### Make sure you don't double-import the api.js script
190 | Importing the JS SDK twice can cause unpredictable behavior, so don't do a direct import separately if you are using react-hcaptcha.
191 |
192 | 3. #### Make sure you are using `reCaptchaCompat=false` if you have the reCAPTCHA JS loaded on the same page.
193 | The hCaptcha "compatibility mode" will interfere with reCAPTCHA, as it adds properties with the same name. If for any reason you are running both hCaptcha and reCAPTCHA in parallel (we recommend only running hCaptcha) then please disable our compatibility mode.
194 |
195 |
196 | ### Sentry
197 |
198 | If the `sentry` flag is enabled, the upstream `hcaptcha-loader` package expects the Sentry SDK, version 8.x or later.
199 |
200 | If you have an older `@sentry/browser` client version on your site, it may take precedence over the bundled version. In this case you may see a console error like "g.setPropagationContext is not a function" due to the hcaptcha-loader trying to call methods only available on newer Sentry clients.
201 |
202 | To resolve this, update the version of the Sentry client you are including on your site to 8.x or higher.
203 |
204 | You can avoid this issue by setting the `sentry` prop to `false`.
205 |
206 |
207 | ---
208 | ### Contributing
209 |
210 | #### Scripts
211 |
212 | * `npm run start` - will start the demo app with hot reload
213 | * `npm run test` - will test the library: unit tests
214 | * `npm run build` - will build the production version
215 |
216 |
217 | #### Publishing
218 |
219 | To publish a new version, follow the next steps:
220 | 1. Bump the version in `package.json`
221 | 2. Create a [Github Release](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository#creating-a-release) with version from step 1 **without** a prefix such as `v` (e.g. `1.0.3`)
222 | * `publish` workflow will be triggered which will: build, test and deploy the package to the [npm @hcaptcha/react-hcaptcha](https://www.npmjs.com/package/@hcaptcha/react-hcaptcha).
223 |
224 |
225 | #### Running locally for development
226 |
227 | Please see: [Local Development Notes](https://docs.hcaptcha.com/#localdev).
228 |
229 | Summary:
230 |
231 | ```
232 | sudo echo "127.0.0.1 fakelocal.com" >> /private/etc/hosts
233 | npm start -- --disable-host-check
234 | ```
235 |
236 | open [http://fakelocal.com:9000](http://fakelocal.com:9000) to start the example.
237 |
--------------------------------------------------------------------------------
/demo/app/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import { AsyncExample, ClassExample, FrameExample } from './examples';
4 | import { CustomFrame } from './components';
5 |
6 |
7 | export function App() {
8 | const [frame, setFrame] = useState(null);
9 | const [frameDocument, setFrameDocument] = useState(frame);
10 |
11 | useEffect(() => {
12 | const onLoad = () => {
13 | const frame = document.getElementById('example-frame');
14 | setFrame(frame);
15 | setFrameDocument(frame.contentWindow.document);
16 | };
17 |
18 | if (document.readyState === 'complete') {
19 | onLoad();
20 | } else {
21 | window.addEventListener('load', onLoad, false);
22 | return () => window.removeEventListener('load', onLoad);
23 | }
24 | }, [setFrame]);
25 |
26 | return (
27 |
28 |
29 |
HCaptcha React Demo
30 |
31 | Set your sitekey and onVerify callback as props, and drop into your form. From here, we'll take care of the rest.
32 |