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

Commit

Permalink
Merge pull request #6401 from Palid/fix/15142/fix-grecaptcha-race-con…
Browse files Browse the repository at this point in the history
…dition
  • Loading branch information
t3chguy committed Jul 20, 2021
2 parents 7ea17ae + 44b8bac commit 91cf27e
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 32 deletions.
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

0 comments on commit 91cf27e

Please sign in to comment.