diff --git a/demos/client-bundle-example/src/index.html b/demos/client-bundle-example/src/index.html index d1f0c0f325..1a4fd27524 100644 --- a/demos/client-bundle-example/src/index.html +++ b/demos/client-bundle-example/src/index.html @@ -130,15 +130,6 @@

Example Login Form

-
- -
-
diff --git a/packages/procaptcha-bundle/src/index.tsx b/packages/procaptcha-bundle/src/index.tsx index 35ed493e48..03f1d8b321 100644 --- a/packages/procaptcha-bundle/src/index.tsx +++ b/packages/procaptcha-bundle/src/index.tsx @@ -88,6 +88,7 @@ declare global { const start = () => { // onLoadUrlCallback defines the name of the callback function to be called when the script is loaded // onRenderExplicit takes values of either explicit or implicit + console.log("Starting Procaptcha"); const { onloadUrlCallback, renderExplicit } = extractParams(BUNDLE_NAME); // Render the Procaptcha component implicitly if renderExplicit is not set to explicit diff --git a/packages/procaptcha-bundle/src/util/defaultCallbacks.ts b/packages/procaptcha-bundle/src/util/defaultCallbacks.ts index 01cd8ebedd..c4a5289327 100644 --- a/packages/procaptcha-bundle/src/util/defaultCallbacks.ts +++ b/packages/procaptcha-bundle/src/util/defaultCallbacks.ts @@ -17,6 +17,7 @@ import { type ProcaptchaToken, } from "@prosopo/types"; import { getParentForm } from "./form.js"; +import { reset } from "../index.js"; export const getWindowCallback = (callbackName: string) => { // biome-ignore lint/suspicious/noExplicitAny: TODO fix any @@ -54,6 +55,7 @@ export const getDefaultCallbacks = (element: Element) => ({ }, onReset: () => { removeProcaptchaResponse(); + reset(); console.log("Captcha widget reset"); }, }); @@ -197,14 +199,23 @@ export function setUserCallbacks( // reset callback if (renderOptions?.["reset-callback"]) { if (typeof renderOptions["reset-callback"] === "function") { - callbacks.onReset = renderOptions["reset-callback"]; + const fn = renderOptions["reset-callback"]; + callbacks.onReset = () => { + removeProcaptchaResponse(); + reset(); + fn(); + }; } else { const onResetCallbackName = typeof renderOptions?.["reset-callback"] === "string" ? renderOptions?.["reset-callback"] : element.getAttribute("data-reset-callback"); if (onResetCallbackName) - callbacks.onReset = getWindowCallback(onResetCallbackName); + callbacks.onReset = () => { + removeProcaptchaResponse(); + reset(); + getWindowCallback(onResetCallbackName)(); + }; } } } diff --git a/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx b/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx index 2fd3e4fd35..89d6bf6546 100644 --- a/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx +++ b/packages/procaptcha-frictionless/src/ProcaptchaFrictionless.tsx @@ -67,50 +67,66 @@ export const ProcaptchaFrictionless = ({ callbacks, detectBot = customDetectBot, }: ProcaptchaFrictionlessProps) => { - const [componentToRender, setComponentToRender] = useState( - , - ); + const [frictionlessLoading, setFrictionlessLoading] = useState(false); + const configOutput = ProcaptchaConfigSchema.parse(config); - useEffect(() => { - const configOutput = ProcaptchaConfigSchema.parse(config); + const frictionlessConfig = { + ...config, + startOnRender: true, + }; - const detectAndSetComponent = async () => { - try { - const result = await detectBot(configOutput); + const detectAndSetComponent = async () => { + setComponentToRender( + {}} + />, + ); - const frictionlessState: FrictionlessState = { - provider: result.provider, - sessionId: result.sessionId, - userAccount: result.userAccount, - }; + try { + const result = await detectBot(configOutput); - if (result.captchaType === "image") { - setComponentToRender( - , - ); - } else { - setComponentToRender( - , - ); - } - } catch (error) { - console.error(error); + const frictionlessState: FrictionlessState = { + provider: result.provider, + sessionId: result.sessionId, + userAccount: result.userAccount, + }; + + if (result.captchaType === "image") { setComponentToRender( - , + , + ); + } else { + setComponentToRender( + , ); } - }; + } catch (error) { + console.error(error); + setComponentToRender( + , + ); + } + }; - detectAndSetComponent(); - }, [config, callbacks, detectBot, config.language]); + const [componentToRender, setComponentToRender] = useState( + , + ); return componentToRender; }; diff --git a/packages/procaptcha-pow/src/components/ProcaptchaPoW.tsx b/packages/procaptcha-pow/src/components/ProcaptchaPoW.tsx index a6d2bbbd18..3fb947e290 100644 --- a/packages/procaptcha-pow/src/components/ProcaptchaPoW.tsx +++ b/packages/procaptcha-pow/src/components/ProcaptchaPoW.tsx @@ -30,6 +30,8 @@ export const ProcaptchaPow = (props: ProcaptchaProps) => ( {}} /> } > diff --git a/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx b/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx index 7e3bbc0454..7fc3830b24 100644 --- a/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx +++ b/packages/procaptcha-pow/src/components/ProcaptchaWidget.tsx @@ -56,6 +56,11 @@ const Procaptcha = (props: ProcaptchaProps) => { } }, [config.language]); + if (config.startOnRender) { + manager.current.start(); + config.startOnRender = false; + } + return (
( {}} /> } > diff --git a/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx b/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx index 9581a8a4d3..c18b81f4db 100644 --- a/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx +++ b/packages/procaptcha-react/src/components/ProcaptchaWidget.tsx @@ -54,6 +54,11 @@ const ProcaptchaWidget = (props: ProcaptchaProps) => { } }, [config.language]); + if (config.startOnRender) { + manager.start(); + config.startOnRender = false; + } + return (
=> { isHuman: false, captchaApi: undefined, account: undefined, - // don't handle timeout here, this should be handled by the state management }; }; @@ -112,7 +111,6 @@ export function Manager( } await cryptoWaitReady(); - resetState(); // set the loading flag to true (allow UI to show some sort of loading / pending indicator while we get the captcha process going) updateState({ loading: true }); updateState({ @@ -158,26 +156,12 @@ export function Manager( throw new ProsopoDatasetError("DEVELOPER.PROVIDER_NO_CAPTCHA"); } - // setup timeout, taking the timeout from the individual captcha or the global default - const timeMillis: number = challenge.captchas - .map( - (captcha) => - captcha.timeLimitMs || config.captchas.image.challengeTimeout, - ) - .reduce((a: number, b: number) => a + b); - const timeout = setTimeout(() => { - events.onChallengeExpired(); - // expired, disallow user's claim to be human - updateState({ isHuman: false, showModal: false, loading: false }); - }, timeMillis); - // update state with new challenge updateState({ index: 0, solutions: challenge.captchas.map(() => []), challenge, showModal: true, - timeout, }); } }, @@ -191,9 +175,6 @@ export function Manager( const submit = async () => { await providerRetry( async () => { - // disable the time limit, user has submitted their solution in time - clearTimeout(); - if (!state.challenge) { throw new ProsopoError("CAPTCHA.NO_CAPTCHA", { context: { error: "Cannot submit, no Captcha found in state" }, @@ -296,7 +277,6 @@ export function Manager( }, }), ); - setValidChallengeTimeout(); } else { events.onFailed(); } @@ -309,17 +289,15 @@ export function Manager( }; const cancel = async () => { - // disable the time limit - clearTimeout(); // abandon the captcha process resetState(); // trigger the onClose event events.onClose(); + // trigger full reset + callbacks?.onReset?.(); }; const reload = async () => { - // disable the time limit - clearTimeout(); // abandon the captcha process resetState(); // trigger the onClose event @@ -386,28 +364,7 @@ export function Manager( return new ProviderApi(providerUrl, config.account.address); }; - const clearTimeout = () => { - // clear the timeout - window.clearTimeout(Number(state.timeout)); - // then clear the timeout from the state - updateState({ timeout: undefined }); - }; - - const setValidChallengeTimeout = () => { - const timeMillis: number = configOptional.captchas.image.solutionTimeout; - const successfullChallengeTimeout = setTimeout(() => { - // Human state expired, disallow user's claim to be human - updateState({ isHuman: false }); - - events.onExpired(); - }, timeMillis); - - updateState({ successfullChallengeTimeout }); - }; - const resetState = () => { - // clear timeout just in case a timer is still active (shouldn't be) - clearTimeout(); updateState(defaultState()); events.onReset(); }; diff --git a/packages/types/src/config/config.ts b/packages/types/src/config/config.ts index 6d9c4d3b72..e4b9168137 100644 --- a/packages/types/src/config/config.ts +++ b/packages/types/src/config/config.ts @@ -261,6 +261,7 @@ export const ProcaptchaConfigSchema = ProsopoClientConfigSchema.and( theme: ThemeType.optional().default("light"), captchas: CaptchaTimeoutSchema.optional().default(defaultCaptchaTimeouts), language: LanguageSchema.optional(), + startOnRender: boolean().optional().default(false), }), ); diff --git a/packages/types/src/procaptcha/props.ts b/packages/types/src/procaptcha/props.ts index 9128ef61ff..73ffc37c7c 100644 --- a/packages/types/src/procaptcha/props.ts +++ b/packages/types/src/procaptcha/props.ts @@ -32,3 +32,8 @@ export interface ProcaptchaProps { callbacks?: Partial; frictionlessState?: FrictionlessState; } + +export interface ProcaptchaPlaceholderProps extends ProcaptchaProps { + detectAndSetComponent: () => void; + frictionlessLoading: boolean; +} diff --git a/packages/web-components/package.json b/packages/web-components/package.json index 37973c5329..1a7ddb5355 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -38,6 +38,8 @@ }, "devDependencies": { "@prosopo/config": "2.1.14", + "@prosopo/locale-browser": "2.1.14", + "@prosopo/types": "2.1.14", "@vitest/coverage-v8": "2.1.1", "concurrently": "9.0.1", "del-cli": "6.0.0", diff --git a/packages/web-components/src/CaptchaPlaceholder.tsx b/packages/web-components/src/CaptchaPlaceholder.tsx index 60e2b68bc7..25bca11430 100644 --- a/packages/web-components/src/CaptchaPlaceholder.tsx +++ b/packages/web-components/src/CaptchaPlaceholder.tsx @@ -13,7 +13,9 @@ // limitations under the License. /** @jsxImportSource @emotion/react */ -import type { ProcaptchaProps } from "@prosopo/types"; +import { useTranslation } from "@prosopo/locale-browser"; +import type { ProcaptchaPlaceholderProps } from "@prosopo/types"; +import Checkbox from "./Checkbox.js"; import { ContainerDiv, WidthBasedStylesDiv } from "./Containers.js"; import { LoadingSpinner } from "./LoadingSpinner.js"; import Logo from "./Logo.js"; @@ -32,9 +34,12 @@ import { darkTheme, lightTheme } from "./theme.js"; export const ProcaptchaPlaceholder = ({ config, callbacks, -}: ProcaptchaProps) => { + frictionlessLoading, + detectAndSetComponent, +}: ProcaptchaPlaceholderProps) => { const themeColor = config.theme === "light" ? "light" : "dark"; const theme = config.theme === "light" ? lightTheme : darkTheme; + const { t } = useTranslation(); return (
- + {frictionlessLoading ? ( + + ) : ( + + )}