mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
@@ -56,6 +56,7 @@ export default function useFetch() {
|
||||
statusText: response.statusText,
|
||||
rateLimits: {
|
||||
bypassOptions: response.headers.get('bypass-429-option'),
|
||||
reset: response.headers.get('x-ratelimit-reset'),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ test('too many requests +@mobile', async({ render }) => {
|
||||
const error = {
|
||||
message: 'Too many requests',
|
||||
cause: { status: 429 },
|
||||
} as Error;
|
||||
rateLimits: { bypassOptions: 'temporary_token', reset: '42000' },
|
||||
} as unknown as Error;
|
||||
const component = await render(<AppError error={ error }/>);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
@@ -80,7 +80,12 @@ const AppError = ({ error, className }: Props) => {
|
||||
case 429: {
|
||||
const rateLimits = getErrorProp(error, 'rateLimits');
|
||||
const bypassOptions = typeof rateLimits === 'object' && rateLimits && 'bypassOptions' in rateLimits ? rateLimits.bypassOptions : undefined;
|
||||
return <AppErrorTooManyRequests bypassOptions={ typeof bypassOptions === 'string' ? bypassOptions : undefined }/>;
|
||||
const reset = typeof rateLimits === 'object' && rateLimits && 'reset' in rateLimits ? rateLimits.reset : undefined;
|
||||
return (
|
||||
<AppErrorTooManyRequests
|
||||
bypassOptions={ typeof bypassOptions === 'string' ? bypassOptions : undefined }
|
||||
reset={ typeof reset === 'string' ? reset : undefined }/>
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@@ -6,17 +6,31 @@ import buildUrl from 'lib/api/buildUrl';
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import { Button } from 'toolkit/chakra/button';
|
||||
import { toaster } from 'toolkit/chakra/toaster';
|
||||
import { SECOND } from 'toolkit/utils/consts';
|
||||
import { apos } from 'toolkit/utils/htmlEntities';
|
||||
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
|
||||
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
|
||||
|
||||
import AppErrorIcon from '../AppErrorIcon';
|
||||
import AppErrorTitle from '../AppErrorTitle';
|
||||
|
||||
interface Props {
|
||||
bypassOptions?: string;
|
||||
function formatTimeLeft(timeLeft: number) {
|
||||
const hours = Math.floor(timeLeft / 3600);
|
||||
const minutes = Math.floor((timeLeft % 3600) / 60);
|
||||
const seconds = timeLeft % 60;
|
||||
|
||||
return `${ hours.toString().padStart(2, '0') }h ${ minutes.toString().padStart(2, '0') }m ${ seconds.toString().padStart(2, '0') }s`;
|
||||
}
|
||||
|
||||
const AppErrorTooManyRequests = ({ bypassOptions }: Props) => {
|
||||
interface Props {
|
||||
bypassOptions?: string;
|
||||
reset?: string;
|
||||
}
|
||||
|
||||
const AppErrorTooManyRequests = ({ bypassOptions, reset }: Props) => {
|
||||
|
||||
const [ timeLeft, setTimeLeft ] = React.useState(reset ? Math.ceil(Number(reset) / SECOND) : undefined);
|
||||
|
||||
const fetch = useFetch();
|
||||
const recaptcha = useReCaptcha();
|
||||
|
||||
@@ -52,19 +66,54 @@ const AppErrorTooManyRequests = ({ bypassOptions }: Props) => {
|
||||
}
|
||||
}, [ recaptcha, fetch ]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (reset === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
setTimeLeft((prev) => {
|
||||
if (prev && prev > 1) {
|
||||
return prev - 1;
|
||||
}
|
||||
|
||||
window.clearInterval(interval);
|
||||
window.location.reload();
|
||||
|
||||
return 0;
|
||||
});
|
||||
}, SECOND);
|
||||
|
||||
return () => {
|
||||
window.clearInterval(interval);
|
||||
};
|
||||
}, [ reset ]);
|
||||
|
||||
if (!config.services.reCaptchaV2.siteKey) {
|
||||
throw new Error('reCAPTCHA V2 site key is not set');
|
||||
}
|
||||
|
||||
const text = (() => {
|
||||
if (timeLeft === undefined && bypassOptions === 'no_bypass') {
|
||||
return 'Rate limit exceeded.';
|
||||
}
|
||||
|
||||
const timeLeftText = timeLeft !== undefined ? `wait ${ formatTimeLeft(timeLeft) } ` : '';
|
||||
const bypassText = bypassOptions !== 'no_bypass' ? `verify you${ apos }re human ` : '';
|
||||
const orText = timeLeft !== undefined && bypassOptions !== 'no_bypass' ? 'OR ' : '';
|
||||
|
||||
return `Rate limit exceeded. Please ${ timeLeftText }${ orText }${ bypassText }before making another request.`;
|
||||
})();
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppErrorIcon statusCode={ 429 }/>
|
||||
<AppErrorTitle title="Too many requests"/>
|
||||
<Text color="text.secondary" mt={ 3 }>
|
||||
You have exceeded the request rate for a given time period. Please reduce the number of requests and try again soon.
|
||||
{ text }
|
||||
</Text>
|
||||
<ReCaptcha { ...recaptcha }/>
|
||||
{ bypassOptions !== 'no_bypass' && <Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>Try again</Button> }
|
||||
{ bypassOptions !== 'no_bypass' && <Button onClick={ handleSubmit } disabled={ recaptcha.isInitError } mt={ 8 }>I'm not a robot</Button> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user