@prosopo/procaptcha-common 2.9.23 → 2.10.18

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.
Files changed (54) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +22 -15
  2. package/.turbo/turbo-build$colon$tsc.log +22 -16
  3. package/.turbo/turbo-build.log +22 -16
  4. package/CHANGELOG.md +355 -0
  5. package/dist/cjs/elements/window.cjs +7 -0
  6. package/dist/cjs/index.cjs +5 -0
  7. package/dist/cjs/reactComponents/Checkbox.cjs +11 -7
  8. package/dist/cjs/reactComponents/Honeypot.cjs +67 -0
  9. package/dist/cjs/reactComponents/TestModeBanner.cjs +47 -0
  10. package/dist/elements/window.d.ts +1 -0
  11. package/dist/elements/window.d.ts.map +1 -1
  12. package/dist/elements/window.js +8 -1
  13. package/dist/elements/window.js.map +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/reactComponents/Checkbox.d.ts.map +1 -1
  19. package/dist/reactComponents/Checkbox.js +11 -7
  20. package/dist/reactComponents/Checkbox.js.map +1 -1
  21. package/dist/reactComponents/Honeypot.d.ts +6 -0
  22. package/dist/reactComponents/Honeypot.d.ts.map +1 -0
  23. package/dist/reactComponents/Honeypot.js +67 -0
  24. package/dist/reactComponents/Honeypot.js.map +1 -0
  25. package/dist/reactComponents/TestModeBanner.d.ts +7 -0
  26. package/dist/reactComponents/TestModeBanner.d.ts.map +1 -0
  27. package/dist/reactComponents/TestModeBanner.js +47 -0
  28. package/dist/reactComponents/TestModeBanner.js.map +1 -0
  29. package/dist/tests/window.test.js +22 -1
  30. package/dist/tests/window.test.js.map +1 -1
  31. package/package.json +11 -8
  32. package/src/callbacks/defaultCallbacks.ts +197 -0
  33. package/src/callbacks/defaultEvents.ts +21 -0
  34. package/src/elements/form.ts +41 -0
  35. package/src/elements/window.ts +39 -0
  36. package/src/extensionLoader.ts +17 -0
  37. package/src/index.ts +24 -0
  38. package/src/providers.ts +49 -0
  39. package/src/reactComponents/Checkbox.tsx +203 -0
  40. package/src/reactComponents/Honeypot.tsx +133 -0
  41. package/src/reactComponents/Reload.tsx +99 -0
  42. package/src/reactComponents/TestModeBanner.tsx +75 -0
  43. package/src/state/builder.ts +137 -0
  44. package/src/tests/defaultCallbacks.test.ts +372 -0
  45. package/src/tests/defaultEvents.test.ts +80 -0
  46. package/src/tests/extensionLoader.test.ts +41 -0
  47. package/src/tests/form.test.ts +154 -0
  48. package/src/tests/providers.test.ts +175 -0
  49. package/src/tests/state-builder.test.ts +264 -0
  50. package/src/tests/window.test.ts +137 -0
  51. package/tsconfig.cjs.json +32 -0
  52. package/tsconfig.json +33 -0
  53. package/tsconfig.tsbuildinfo +1 -0
  54. package/tsconfig.types.json +9 -0
@@ -0,0 +1,197 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import {
15
+ ApiParams,
16
+ type Callbacks,
17
+ type ProcaptchaRenderOptions,
18
+ type ProcaptchaToken,
19
+ } from "@prosopo/types";
20
+ import { getParentForm, removeProcaptchaResponse } from "../elements/form.js";
21
+ import { getWindowCallback } from "../elements/window.js";
22
+
23
+ export const getDefaultCallbacks = (element?: Element): Callbacks => ({
24
+ onHuman: (token: ProcaptchaToken) => handleOnHuman(token, element),
25
+ onChallengeExpired: () => {
26
+ removeProcaptchaResponse();
27
+ console.log("Challenge expired");
28
+ },
29
+ onExtensionNotFound: () => {
30
+ console.error("Extension not found");
31
+ },
32
+ onExpired: () => {
33
+ removeProcaptchaResponse();
34
+ },
35
+ onError: (error: Error) => {
36
+ removeProcaptchaResponse();
37
+ console.error(error);
38
+ },
39
+ onClose: () => {
40
+ console.log("Challenge closed");
41
+ },
42
+ onOpen: () => {
43
+ console.log("Challenge opened");
44
+ },
45
+ onFailed: () => {
46
+ alert("Captcha challenge failed. Please try again");
47
+ console.log("Challenge failed");
48
+ },
49
+ onReset: () => {
50
+ removeProcaptchaResponse();
51
+ console.log("Captcha widget reset");
52
+ },
53
+ onReload: () => {
54
+ console.log("Challenge reloaded");
55
+ },
56
+ });
57
+
58
+ /**
59
+ * Set the a user callback function for an element. Data tags take precedence over renderOptions.
60
+ */
61
+ const getUserCallback = (
62
+ callback: string,
63
+ element: Element,
64
+ callbackFnOrName: string | ((token: string) => void) | undefined,
65
+ ) => {
66
+ const callbackFnName = element.getAttribute(`data-${callback}`);
67
+ if (callbackFnName) {
68
+ const callbackFn = getWindowCallback(callbackFnName);
69
+ if (callbackFn) {
70
+ return callbackFn;
71
+ }
72
+ }
73
+ if (typeof callbackFnOrName === "function") {
74
+ return callbackFnOrName;
75
+ }
76
+ if (typeof callbackFnOrName === "string") {
77
+ return getWindowCallback(callbackFnOrName);
78
+ }
79
+ };
80
+
81
+ export function setUserCallbacks(
82
+ renderOptions: ProcaptchaRenderOptions | undefined,
83
+ callbacks: Callbacks,
84
+ element: Element,
85
+ ) {
86
+ const humanCallback = getUserCallback(
87
+ "callback",
88
+ element,
89
+ renderOptions?.callback,
90
+ );
91
+ if (humanCallback) {
92
+ // wrap the user's callback in a function that also calls handleOnHuman
93
+ callbacks.onHuman = (token: ProcaptchaToken) => {
94
+ handleOnHuman(token, element);
95
+ humanCallback(token);
96
+ };
97
+ }
98
+
99
+ const chalExpiredCallback = getUserCallback(
100
+ "chalexpired-callback",
101
+ element,
102
+ renderOptions?.["chalexpired-callback"],
103
+ );
104
+ if (chalExpiredCallback) {
105
+ callbacks.onChallengeExpired = () => {
106
+ removeProcaptchaResponse();
107
+ chalExpiredCallback();
108
+ };
109
+ }
110
+
111
+ const expiredCallback = getUserCallback(
112
+ "expired-callback",
113
+ element,
114
+ renderOptions?.["expired-callback"],
115
+ );
116
+ if (expiredCallback) {
117
+ callbacks.onExpired = () => {
118
+ removeProcaptchaResponse();
119
+ expiredCallback();
120
+ };
121
+ }
122
+
123
+ const errorCallback = getUserCallback(
124
+ "error-callback",
125
+ element,
126
+ renderOptions?.["error-callback"],
127
+ );
128
+ if (errorCallback) {
129
+ callbacks.onError = (error: Error) => {
130
+ removeProcaptchaResponse();
131
+ errorCallback(error);
132
+ };
133
+ }
134
+
135
+ const closeCallback = getUserCallback(
136
+ "close-callback",
137
+ element,
138
+ renderOptions?.["close-callback"],
139
+ );
140
+ if (closeCallback) {
141
+ callbacks.onClose = () => {
142
+ closeCallback();
143
+ };
144
+ }
145
+
146
+ const openCallback = getUserCallback(
147
+ "open-callback",
148
+ element,
149
+ renderOptions?.["open-callback"],
150
+ );
151
+ if (openCallback) {
152
+ callbacks.onOpen = () => {
153
+ openCallback();
154
+ };
155
+ }
156
+
157
+ const failedCallback = getUserCallback(
158
+ "failed-callback",
159
+ element,
160
+ renderOptions?.["failed-callback"],
161
+ );
162
+ if (failedCallback) {
163
+ callbacks.onFailed = () => {
164
+ failedCallback();
165
+ };
166
+ }
167
+
168
+ const resetCallback = getUserCallback(
169
+ "reset-callback",
170
+ element,
171
+ renderOptions?.["reset-callback"],
172
+ );
173
+ if (resetCallback) {
174
+ callbacks.onReset = () => {
175
+ removeProcaptchaResponse();
176
+ resetCallback();
177
+ };
178
+ }
179
+ }
180
+
181
+ const handleOnHuman = (token: ProcaptchaToken, element?: Element) => {
182
+ removeProcaptchaResponse();
183
+ if (element) {
184
+ const form = getParentForm(element);
185
+
186
+ if (!form) {
187
+ console.error("Parent form not found for the element:", element);
188
+ return;
189
+ }
190
+
191
+ const input = document.createElement("input");
192
+ input.type = "hidden";
193
+ input.name = ApiParams.procaptchaResponse;
194
+ input.value = token;
195
+ form.appendChild(input);
196
+ }
197
+ };
@@ -0,0 +1,21 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type { Callbacks } from "@prosopo/types";
16
+ import { getDefaultCallbacks } from "./defaultCallbacks.js";
17
+
18
+ export const getDefaultEvents = (callbacks: Partial<Callbacks>) => ({
19
+ ...getDefaultCallbacks(undefined),
20
+ ...callbacks,
21
+ });
@@ -0,0 +1,41 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { ApiParams } from "@prosopo/types";
16
+
17
+ type ParentForm = HTMLFormElement | null;
18
+
19
+ export function getParentForm(widgetElement: Element): ParentForm {
20
+ const parentForm = widgetElement.closest("form") as ParentForm;
21
+
22
+ if (parentForm) {
23
+ return parentForm;
24
+ }
25
+
26
+ // fallback for widgets inside a shadow DOM
27
+
28
+ const rootWidgetNode = widgetElement.getRootNode();
29
+ if (rootWidgetNode instanceof ShadowRoot) {
30
+ return rootWidgetNode.host.closest("form") as ParentForm;
31
+ }
32
+
33
+ return null;
34
+ }
35
+
36
+ export const removeProcaptchaResponse = () => {
37
+ const element = Array.from(
38
+ document.getElementsByName(ApiParams.procaptchaResponse),
39
+ );
40
+ element.map((el) => el.remove());
41
+ };
@@ -0,0 +1,39 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ export const getWindowCallback = (callbackName: string) => {
16
+ // biome-ignore lint/suspicious/noExplicitAny: TODO fix any
17
+ const fn = (window as any)[callbackName.replace("window.", "")];
18
+ if (typeof fn !== "function") {
19
+ throw new Error(
20
+ `Callback ${callbackName} is not defined on the window object`,
21
+ );
22
+ }
23
+ return fn;
24
+ };
25
+
26
+ /**
27
+ * Procaptcha depends on secure-context-only browser APIs (e.g. SubtleCrypto)
28
+ * to run a challenge. These are unavailable when the widget is served over
29
+ * plain HTTP, which otherwise surfaces as a cryptic provider-selection
30
+ * failure. The browser already treats HTTPS and localhost as secure contexts,
31
+ * so this only flags genuine HTTP origins. Non-browser (SSR) environments are
32
+ * treated as secure since the HTTP restriction does not apply there.
33
+ */
34
+ export const isSecureBrowserContext = (): boolean => {
35
+ if (typeof window === "undefined") {
36
+ return true;
37
+ }
38
+ return window.isSecureContext === true;
39
+ };
@@ -0,0 +1,17 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ export const ExtensionLoader = async (web2: boolean) =>
15
+ web2
16
+ ? (await import("@prosopo/account/extension/ExtensionWeb2")).default
17
+ : (await import("@prosopo/account/extension/ExtensionWeb3")).default;
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ export * from "./providers.js";
16
+ export * from "./state/builder.js";
17
+ export * from "./callbacks/defaultCallbacks.js";
18
+ export * from "./callbacks/defaultEvents.js";
19
+ export * from "./extensionLoader.js";
20
+ export * from "./elements/window.js";
21
+ export * from "./reactComponents/Reload.js";
22
+ export * from "./reactComponents/Checkbox.js";
23
+ export * from "./reactComponents/Honeypot.js";
24
+ export * from "./reactComponents/TestModeBanner.js";
@@ -0,0 +1,49 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { getRandomActiveProvider } from "@prosopo/load-balancer";
16
+ import type { EnvironmentTypes } from "@prosopo/types";
17
+
18
+ export const getProcaptchaRandomActiveProvider = async (
19
+ defaultEnvironment: EnvironmentTypes,
20
+ ) => {
21
+ const randomNumberU8a = window.crypto.getRandomValues(new Uint8Array(10));
22
+ const randomNumber = randomNumberU8a.reduce((a, b) => a + b, 0);
23
+ return await getRandomActiveProvider(defaultEnvironment, randomNumber);
24
+ };
25
+
26
+ export const providerRetry = async (
27
+ currentFn: () => Promise<void>,
28
+ retryFn: () => Promise<void>,
29
+ stateReset: () => void,
30
+ attemptCount: number,
31
+ retryMax: number,
32
+ ) => {
33
+ try {
34
+ await currentFn();
35
+ } catch (err) {
36
+ if (attemptCount >= retryMax) {
37
+ console.error(err);
38
+ console.error(
39
+ `Max retries (${attemptCount} of ${retryMax}) reached, aborting`,
40
+ );
41
+ return stateReset();
42
+ }
43
+ console.error(err);
44
+ // hit an error, disallow user's claim to be human
45
+ stateReset();
46
+ // trigger a retry to attempt a new provider until it passes
47
+ await retryFn();
48
+ }
49
+ };
@@ -0,0 +1,203 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { css } from "@emotion/react";
16
+ import styled from "@emotion/styled";
17
+ import {
18
+ type Theme,
19
+ WIDGET_CHECKBOX_SPINNER_CSS_CLASS,
20
+ } from "@prosopo/widget-skeleton";
21
+ import {
22
+ type ButtonHTMLAttributes,
23
+ type CSSProperties,
24
+ type FC,
25
+ useMemo,
26
+ useState,
27
+ } from "react";
28
+
29
+ interface CheckboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
30
+ theme: Theme;
31
+ checked: boolean;
32
+ // biome-ignore lint/suspicious/noExplicitAny: don't know what it will be
33
+ onChange: (event: any) => Promise<void>;
34
+ labelText: string;
35
+ error?: string;
36
+ loading: boolean;
37
+ }
38
+
39
+ const checkboxBefore = css`{
40
+ &:before {
41
+ content: '""';
42
+ position: absolute;
43
+ height: 100%;
44
+ width: 100%;
45
+ }
46
+ }`;
47
+
48
+ const baseStyle: CSSProperties = {
49
+ width: "28px",
50
+ height: "28px",
51
+ minWidth: "14px",
52
+ minHeight: "14px",
53
+ top: "auto",
54
+ left: "auto",
55
+ opacity: "1",
56
+ borderRadius: "12.5%",
57
+ appearance: "none",
58
+ cursor: "pointer",
59
+ margin: "0",
60
+ borderStyle: "solid",
61
+ borderWidth: "1px",
62
+ };
63
+
64
+ const ID_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
65
+
66
+ const FAQ_LINK = process.env.PROSOPO_DOCS_URL
67
+ ? `${new URL(`${process.env.PROSOPO_DOCS_URL}/en/basics/faq/`).href}/`
68
+ : "https://docs.prosopo.io/en/basics/faq/";
69
+
70
+ const generateRandomId = () => {
71
+ return Array.from(
72
+ { length: 8 },
73
+ () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)],
74
+ ).join("");
75
+ };
76
+
77
+ interface ResponsiveLabelProps {
78
+ htmFor?: string;
79
+ }
80
+
81
+ export const Checkbox: FC<CheckboxProps> = ({
82
+ theme,
83
+ onChange,
84
+ checked,
85
+ labelText,
86
+ error,
87
+ loading,
88
+ }: CheckboxProps) => {
89
+ const checkboxStyleBase: CSSProperties = {
90
+ ...baseStyle,
91
+ border: `1px solid ${theme.palette.background.contrastText}`,
92
+ };
93
+ const [hover, setHover] = useState(false);
94
+
95
+ const ResponsiveLabel = styled.label<ResponsiveLabelProps>`
96
+ color: ${theme.palette.background.contrastText};
97
+ position: relative;
98
+ display: flex !important;
99
+ cursor: pointer;
100
+ user-select: none;
101
+ font-weight: normal;
102
+ font-family: ${theme.font.fontFamily};
103
+ @container prosopo-widget (max-width: 169px) {
104
+ display: none;
105
+ }
106
+ @container prosopo-widget (min-width: 170px) {
107
+ font-size: 10px;
108
+ }
109
+ @container prosopo-widget (min-width: 220px) {
110
+ font-size: 12px;
111
+ }
112
+ @container prosopo-widget (min-width: 250px) {
113
+ font-size: 14px;
114
+ }
115
+ @container prosopo-widget (min-width: 270px) {
116
+ font-size: 16px;
117
+ }
118
+ `;
119
+
120
+ // biome-ignore lint/correctness/useExhaustiveDependencies: TODO fix
121
+ const checkboxStyle: CSSProperties = useMemo(() => {
122
+ return {
123
+ ...checkboxStyleBase,
124
+ borderColor: hover
125
+ ? theme.palette.background.contrastText
126
+ : theme.palette.border,
127
+ appearance: checked ? "auto" : "none",
128
+ flex: 1,
129
+ margin: "15px",
130
+ minWidth: "28px",
131
+ minHeight: "28px",
132
+ };
133
+ }, [hover, theme, checked]);
134
+ const id = generateRandomId();
135
+
136
+ return (
137
+ <span
138
+ style={{
139
+ display: "inline-flex",
140
+ alignItems: "center",
141
+ minHeight: "58px",
142
+ }}
143
+ >
144
+ {loading ? (
145
+ <div
146
+ className={WIDGET_CHECKBOX_SPINNER_CSS_CLASS}
147
+ aria-label="Loading spinner"
148
+ />
149
+ ) : (
150
+ <input
151
+ name={id}
152
+ id={id}
153
+ onMouseEnter={() => setHover(true)}
154
+ onMouseLeave={() => setHover(false)}
155
+ css={checkboxBefore}
156
+ type={"checkbox"}
157
+ aria-live={"assertive"}
158
+ aria-label={labelText}
159
+ onKeyDown={(e) => {
160
+ if (!e.isTrusted) {
161
+ return;
162
+ }
163
+ if (e.key === "Enter") {
164
+ e.preventDefault();
165
+ e.stopPropagation();
166
+ setHover(false);
167
+ onChange(e);
168
+ }
169
+ }}
170
+ onChange={(e) => {
171
+ if (!e.isTrusted) {
172
+ return;
173
+ }
174
+ e.preventDefault();
175
+ e.stopPropagation();
176
+ setHover(false);
177
+ onChange(e);
178
+ }}
179
+ checked={checked}
180
+ style={checkboxStyle}
181
+ disabled={error !== undefined}
182
+ className={loading ? "prosopo-checkbox__loading-spinner" : ""}
183
+ data-cy={"captcha-checkbox"}
184
+ />
185
+ )}
186
+ {error ? (
187
+ <ResponsiveLabel htmFor={id}>
188
+ <a
189
+ css={{
190
+ color: theme.palette.error.main,
191
+ }}
192
+ href={FAQ_LINK}
193
+ >
194
+ {error}
195
+ </a>
196
+ </ResponsiveLabel>
197
+ ) : (
198
+ <ResponsiveLabel htmFor={id}>{labelText}</ResponsiveLabel>
199
+ )}
200
+ </span>
201
+ );
202
+ };
203
+ export default Checkbox;