@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.
- package/.turbo/turbo-build$colon$cjs.log +22 -15
- package/.turbo/turbo-build$colon$tsc.log +22 -16
- package/.turbo/turbo-build.log +22 -16
- package/CHANGELOG.md +355 -0
- package/dist/cjs/elements/window.cjs +7 -0
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/reactComponents/Checkbox.cjs +11 -7
- package/dist/cjs/reactComponents/Honeypot.cjs +67 -0
- package/dist/cjs/reactComponents/TestModeBanner.cjs +47 -0
- package/dist/elements/window.d.ts +1 -0
- package/dist/elements/window.d.ts.map +1 -1
- package/dist/elements/window.js +8 -1
- package/dist/elements/window.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/reactComponents/Checkbox.d.ts.map +1 -1
- package/dist/reactComponents/Checkbox.js +11 -7
- package/dist/reactComponents/Checkbox.js.map +1 -1
- package/dist/reactComponents/Honeypot.d.ts +6 -0
- package/dist/reactComponents/Honeypot.d.ts.map +1 -0
- package/dist/reactComponents/Honeypot.js +67 -0
- package/dist/reactComponents/Honeypot.js.map +1 -0
- package/dist/reactComponents/TestModeBanner.d.ts +7 -0
- package/dist/reactComponents/TestModeBanner.d.ts.map +1 -0
- package/dist/reactComponents/TestModeBanner.js +47 -0
- package/dist/reactComponents/TestModeBanner.js.map +1 -0
- package/dist/tests/window.test.js +22 -1
- package/dist/tests/window.test.js.map +1 -1
- package/package.json +11 -8
- package/src/callbacks/defaultCallbacks.ts +197 -0
- package/src/callbacks/defaultEvents.ts +21 -0
- package/src/elements/form.ts +41 -0
- package/src/elements/window.ts +39 -0
- package/src/extensionLoader.ts +17 -0
- package/src/index.ts +24 -0
- package/src/providers.ts +49 -0
- package/src/reactComponents/Checkbox.tsx +203 -0
- package/src/reactComponents/Honeypot.tsx +133 -0
- package/src/reactComponents/Reload.tsx +99 -0
- package/src/reactComponents/TestModeBanner.tsx +75 -0
- package/src/state/builder.ts +137 -0
- package/src/tests/defaultCallbacks.test.ts +372 -0
- package/src/tests/defaultEvents.test.ts +80 -0
- package/src/tests/extensionLoader.test.ts +41 -0
- package/src/tests/form.test.ts +154 -0
- package/src/tests/providers.test.ts +175 -0
- package/src/tests/state-builder.test.ts +264 -0
- package/src/tests/window.test.ts +137 -0
- package/tsconfig.cjs.json +32 -0
- package/tsconfig.json +33 -0
- package/tsconfig.tsbuildinfo +1 -0
- 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";
|
package/src/providers.ts
ADDED
|
@@ -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;
|