Files
element-call/src/auth/useRecaptcha.ts
T

123 lines
3.4 KiB
TypeScript
Raw Normal View History

2022-05-04 17:09:48 +01:00
/*
Copyright 2022-2024 New Vector Ltd.
2022-05-04 17:09:48 +01:00
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
2022-05-04 17:09:48 +01:00
*/
2022-01-04 16:00:13 -08:00
import { useEffect, useCallback, useRef, useState } from "react";
2025-02-03 13:28:12 +01:00
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
2022-10-10 09:19:10 -04:00
import { useTranslation } from "react-i18next";
2023-09-25 18:04:34 +01:00
import { logger } from "matrix-js-sdk/src/logger";
2022-10-10 09:19:10 -04:00
import { translatedError } from "../TranslatedError";
2022-05-27 10:37:27 -04:00
declare global {
interface Window {
mxOnRecaptchaLoaded: () => void;
}
}
2022-01-04 16:00:13 -08:00
const RECAPTCHA_SCRIPT_URL =
"https://www.recaptcha.net/recaptcha/api.js?onload=mxOnRecaptchaLoaded&render=explicit";
2022-05-27 10:37:27 -04:00
interface RecaptchaPromiseRef {
resolve: (response: string) => void;
reject: (error: Error) => void;
}
2023-09-22 18:05:13 -04:00
export function useRecaptcha(sitekey?: string): {
execute: () => Promise<string>;
reset: () => void;
recaptchaId: string;
} {
2022-10-10 09:19:10 -04:00
const { t } = useTranslation();
2025-02-03 13:28:12 +01:00
const [recaptchaId] = useState(() => secureRandomString(16));
const promiseRef = useRef<RecaptchaPromiseRef | undefined>(undefined);
2022-01-04 16:00:13 -08:00
useEffect(() => {
2022-05-27 10:37:27 -04:00
if (!sitekey) return;
2022-01-04 16:00:13 -08:00
2023-09-22 18:05:13 -04:00
const onRecaptchaLoaded = (): void => {
2022-05-27 10:37:27 -04:00
if (!document.getElementById(recaptchaId)) return;
2022-01-04 16:00:13 -08:00
window.grecaptcha.render(recaptchaId, {
sitekey,
size: "invisible",
2022-05-27 10:37:27 -04:00
callback: (response: string) => promiseRef.current?.resolve(response),
// eslint-disable-next-line @typescript-eslint/naming-convention
"error-callback": () => promiseRef.current?.reject(new Error()),
2022-01-04 16:00:13 -08:00
});
};
2022-05-27 10:37:27 -04:00
if (typeof window.grecaptcha?.render === "function") {
2022-01-04 16:00:13 -08:00
onRecaptchaLoaded();
} else {
window.mxOnRecaptchaLoaded = onRecaptchaLoaded;
if (!document.querySelector(`script[src="${RECAPTCHA_SCRIPT_URL}"]`)) {
2022-05-27 10:37:27 -04:00
const scriptTag = document.createElement("script") as HTMLScriptElement;
2022-01-04 16:00:13 -08:00
scriptTag.src = RECAPTCHA_SCRIPT_URL;
scriptTag.async = true;
document.body.appendChild(scriptTag);
}
}
}, [recaptchaId, sitekey]);
const execute = useCallback(async (): Promise<string> => {
2022-01-07 11:31:53 -08:00
if (!sitekey) {
2023-06-30 16:43:28 +01:00
return Promise.resolve("");
2022-01-07 11:31:53 -08:00
}
2022-01-04 16:00:13 -08:00
if (!window.grecaptcha) {
2023-09-25 18:04:34 +01:00
logger.log("Recaptcha not loaded");
2023-11-20 13:00:43 +00:00
return Promise.reject(translatedError("recaptcha_not_loaded", t));
2022-01-04 16:00:13 -08:00
}
2022-01-04 17:57:23 -08:00
return new Promise((resolve, reject) => {
const observer = new MutationObserver((mutationsList) => {
for (const item of mutationsList) {
2022-05-27 10:37:27 -04:00
if ((item.target as HTMLElement)?.style?.visibility !== "visible") {
2023-11-20 13:00:43 +00:00
reject(translatedError("recaptcha_dismissed", t));
2022-01-04 17:57:23 -08:00
observer.disconnect();
return;
}
}
});
promiseRef.current = {
2023-09-22 18:05:13 -04:00
resolve: (value): void => {
2022-01-04 17:57:23 -08:00
resolve(value);
observer.disconnect();
},
2023-09-22 18:05:13 -04:00
reject: (error): void => {
2022-01-04 17:57:23 -08:00
reject(error);
observer.disconnect();
},
};
window.grecaptcha.execute().then(
() => {}, // noop
(e) => {
logger.error("Recaptcha execution failed", e);
},
);
2022-01-04 17:57:23 -08:00
2022-05-27 10:37:27 -04:00
const iframe = document.querySelector<HTMLIFrameElement>(
2023-10-11 10:42:04 -04:00
'iframe[src*="recaptcha/api2/bframe"]',
2022-01-04 17:57:23 -08:00
);
if (iframe?.parentNode?.parentNode) {
observer.observe(iframe?.parentNode?.parentNode, {
attributes: true,
});
}
2022-01-04 16:00:13 -08:00
});
2022-10-10 09:19:10 -04:00
}, [sitekey, t]);
2022-01-04 16:00:13 -08:00
const reset = useCallback(() => {
2022-05-27 10:37:27 -04:00
window.grecaptcha?.reset();
}, []);
2022-01-04 16:00:13 -08:00
return { execute, reset, recaptchaId };
2023-09-22 18:05:13 -04:00
}