Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Fix grecaptcha throwing useless error sometimes #6401

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ declare global {
mxUIStore: UIStore;
mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore;
mxOnRecaptchaLoaded?: () => void;
}

interface Document {
Expand All @@ -116,7 +117,7 @@ declare global {
}

interface StorageEstimate {
usageDetails?: {[key: string]: number};
usageDetails?: { [key: string]: number };
}

interface HTMLAudioElement {
Expand Down Expand Up @@ -187,6 +188,21 @@ declare global {
parameterDescriptors?: AudioParamDescriptor[];
}
);

// eslint-disable-next-line no-var
var grecaptcha:
| undefined
| {
reset: (id: string) => void;
render: (
divId: string,
options: {
sitekey: string;
callback: (response: string) => void;
},
) => string;
isReady: () => boolean;
};
}

/* eslint-enable @typescript-eslint/naming-convention */
Original file line number Diff line number Diff line change
Expand Up @@ -15,66 +15,74 @@ limitations under the License.
*/

import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import CountlyAnalytics from "../../../CountlyAnalytics";
import { replaceableComponent } from "../../../utils/replaceableComponent";

const DIV_ID = 'mx_recaptcha';

interface ICaptchaFormProps {
sitePublicKey: string;
onCaptchaResponse: (response: string) => void;
}

interface ICaptchaFormState {
errorText?: string;

}

/**
* A pure UI component which displays a captcha form.
*/
@replaceableComponent("views.auth.CaptchaForm")
export default class CaptchaForm extends React.Component {
static propTypes = {
sitePublicKey: PropTypes.string,

// called with the captcha response
onCaptchaResponse: PropTypes.func,
};

export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICaptchaFormState> {
static defaultProps = {
onCaptchaResponse: () => {},
};

constructor(props) {
private captchaWidgetId?: string;
private recaptchaContainer = createRef<HTMLDivElement>();

constructor(props: ICaptchaFormProps) {
super(props);

this.state = {
errorText: null,
errorText: undefined,
};

this._captchaWidgetId = null;

this._recaptchaContainer = createRef();

CountlyAnalytics.instance.track("onboarding_grecaptcha_begin");
}

componentDidMount() {
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
// so we do this instead.
if (global.grecaptcha) {
if (this.isRecaptchaReady()) {
// already loaded
this._onCaptchaLoaded();
this.onCaptchaLoaded();
} else {
console.log("Loading recaptcha script...");
window.mx_on_recaptcha_loaded = () => {this._onCaptchaLoaded();};
window.mxOnRecaptchaLoaded = () => { this.onCaptchaLoaded(); };
const scriptTag = document.createElement('script');
scriptTag.setAttribute(
'src', `https://www.recaptcha.net/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`,
'src', `https://www.recaptcha.net/recaptcha/api.js?onload=mxOnRecaptchaLoaded&render=explicit`,
);
this._recaptchaContainer.current.appendChild(scriptTag);
this.recaptchaContainer.current.appendChild(scriptTag);
}
}

componentWillUnmount() {
this._resetRecaptcha();
this.resetRecaptcha();
}

// Borrowed directly from: https://github.com/codeep/react-recaptcha-google/commit/e118fa5670fa268426969323b2e7fe77698376ba
private isRecaptchaReady(): boolean {
return typeof window !== "undefined" &&
typeof global.grecaptcha !== "undefined" &&
typeof global.grecaptcha.render === 'function';
}

_renderRecaptcha(divId) {
if (!global.grecaptcha) {
private renderRecaptcha(divId: string) {
if (!this.isRecaptchaReady()) {
console.error("grecaptcha not loaded!");
throw new Error("Recaptcha did not load successfully");
}
Expand All @@ -84,26 +92,26 @@ export default class CaptchaForm extends React.Component {
console.error("No public key for recaptcha!");
throw new Error(
"This server has not supplied enough information for Recaptcha "
+ "authentication");
+ "authentication");
}

console.info("Rendering to %s", divId);
this._captchaWidgetId = global.grecaptcha.render(divId, {
this.captchaWidgetId = global.grecaptcha.render(divId, {
sitekey: publicKey,
callback: this.props.onCaptchaResponse,
});
}

_resetRecaptcha() {
if (this._captchaWidgetId !== null) {
global.grecaptcha.reset(this._captchaWidgetId);
private resetRecaptcha() {
if (this.captchaWidgetId !== null) {
global.grecaptcha.reset(this.captchaWidgetId);
}
}

_onCaptchaLoaded() {
private onCaptchaLoaded() {
console.log("Loaded recaptcha script.");
try {
this._renderRecaptcha(DIV_ID);
this.renderRecaptcha(DIV_ID);
// clear error if re-rendered
this.setState({
errorText: null,
Expand All @@ -128,7 +136,7 @@ export default class CaptchaForm extends React.Component {
}

return (
<div ref={this._recaptchaContainer}>
<div ref={this.recaptchaContainer}>
<p>{ _t(
"This homeserver would like to make sure you are not a robot.",
) }</p>
Expand Down