kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge remote-tracking branch 'origin/main' into ky
commit
82b6ff1743
|
@ -62,8 +62,9 @@
|
||||||
"@reach/rect": "^0.18.0",
|
"@reach/rect": "^0.18.0",
|
||||||
"@reach/tabs": "^0.18.0",
|
"@reach/tabs": "^0.18.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"@sentry/browser": "^7.74.1",
|
"@sentry/browser": "^8.34.0",
|
||||||
"@sentry/react": "^7.74.1",
|
"@sentry/react": "^8.34.0",
|
||||||
|
"@sentry/types": "^8.34.0",
|
||||||
"@soapbox.pub/wasmboy": "^0.8.0",
|
"@soapbox.pub/wasmboy": "^0.8.0",
|
||||||
"@soapbox/weblock": "npm:@jsr/soapbox__weblock",
|
"@soapbox/weblock": "npm:@jsr/soapbox__weblock",
|
||||||
"@tabler/icons": "^3.1.0",
|
"@tabler/icons": "^3.1.0",
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { closeModal } from 'soapbox/actions/modals';
|
||||||
|
import { useApi, useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||||
|
import { captchaSchema, type CaptchaData } from 'soapbox/schemas/captcha';
|
||||||
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
sucessMessage: { id: 'nostr_signup.captcha_message.sucess', defaultMessage: 'Incredible! You\'ve successfully completed the captcha. Let\'s move on to the next step!' },
|
||||||
|
wrongMessage: { id: 'nostr_signup.captcha_message.wrong', defaultMessage: 'Oops! It looks like your captcha response was incorrect. Please try again.' },
|
||||||
|
errorMessage: { id: 'nostr_signup.captcha_message.error', defaultMessage: 'It seems an error has occurred. Please try again. If the problem persists, please contact us.' },
|
||||||
|
misbehavingMessage: { id: 'nostr_signup.captcha_message.misbehaving', defaultMessage: 'It looks like we\'re experiencing issues with the {instance}. Please try again. If the error persists, try again later.' },
|
||||||
|
});
|
||||||
|
|
||||||
|
function getRandomNumber(min: number, max: number): number {
|
||||||
|
return Number((Math.random() * (max - min) + min).toFixed());
|
||||||
|
}
|
||||||
|
|
||||||
|
const useCaptcha = () => {
|
||||||
|
const api = useApi();
|
||||||
|
const instance = useInstance();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
const [captcha, setCaptcha] = useState<CaptchaData>();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||||
|
const [tryAgain, setTryAgain] = useState<boolean>(false);
|
||||||
|
const [yPosition, setYPosition] = useState<number>();
|
||||||
|
const [xPosition, setXPosition] = useState<number>();
|
||||||
|
|
||||||
|
const loadCaptcha = async () => {
|
||||||
|
try {
|
||||||
|
const topI = getRandomNumber(0, (356 - 61));
|
||||||
|
const leftI = getRandomNumber(0, (330 - 61));
|
||||||
|
const { data } = await api.get('/api/v1/ditto/captcha');
|
||||||
|
if (data) {
|
||||||
|
const normalizedData = captchaSchema.parse(data);
|
||||||
|
setCaptcha(normalizedData);
|
||||||
|
setYPosition(topI);
|
||||||
|
setXPosition(leftI);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Error loading captcha:');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadCaptcha();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleChangePosition = (point: { x: number; y: number }) => {
|
||||||
|
setXPosition(point.x);
|
||||||
|
setYPosition(point.y);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
if (captcha) {
|
||||||
|
const result = {
|
||||||
|
x: xPosition,
|
||||||
|
y: yPosition,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.post(`/api/v1/ditto/captcha/${captcha.id}/verify`, result).then(() => {
|
||||||
|
setTryAgain(true);
|
||||||
|
|
||||||
|
dispatch(closeModal('CAPTCHA'));
|
||||||
|
toast.success(messages.sucessMessage);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setTryAgain(true);
|
||||||
|
const error = e as AxiosError;
|
||||||
|
const status = error.request?.status;
|
||||||
|
|
||||||
|
let message;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
message = intl.formatMessage(messages.wrongMessage);
|
||||||
|
break;
|
||||||
|
case 422:
|
||||||
|
message = intl.formatMessage(messages.misbehavingMessage, { instance: instance.title });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = intl.formatMessage(messages.errorMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
toast.error(message);
|
||||||
|
}
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
captcha,
|
||||||
|
loadCaptcha,
|
||||||
|
handleChangePosition,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
tryAgain,
|
||||||
|
yPosition,
|
||||||
|
xPosition,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCaptcha;
|
|
@ -111,6 +111,8 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
|
||||||
}));
|
}));
|
||||||
} else if ((hasComposeContent || hasEventComposeContent) && type === 'CONFIRM') {
|
} else if ((hasComposeContent || hasEventComposeContent) && type === 'CONFIRM') {
|
||||||
dispatch(closeModal('CONFIRM'));
|
dispatch(closeModal('CONFIRM'));
|
||||||
|
} else if (type === 'CAPTCHA') {
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ const SentryFeedbackForm: React.FC<ISentryFeedbackForm> = ({ eventId }) => {
|
||||||
|
|
||||||
await captureSentryFeedback({
|
await captureSentryFeedback({
|
||||||
name: account?.acct,
|
name: account?.acct,
|
||||||
event_id: eventId,
|
associatedEventId: eventId,
|
||||||
comments: feedback,
|
message: feedback,
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
|
|
||||||
setIsSubmitted(true);
|
setIsSubmitted(true);
|
||||||
|
|
|
@ -46,6 +46,7 @@ import {
|
||||||
ZapSplitModal,
|
ZapSplitModal,
|
||||||
ZapInvoiceModal,
|
ZapInvoiceModal,
|
||||||
ZapsModal,
|
ZapsModal,
|
||||||
|
CaptchaModal,
|
||||||
} from 'soapbox/features/ui/util/async-components';
|
} from 'soapbox/features/ui/util/async-components';
|
||||||
|
|
||||||
import ModalLoading from './modal-loading';
|
import ModalLoading from './modal-loading';
|
||||||
|
@ -56,6 +57,7 @@ const MODAL_COMPONENTS: Record<string, React.ExoticComponent<any>> = {
|
||||||
'ACTIONS': ActionsModal,
|
'ACTIONS': ActionsModal,
|
||||||
'BIRTHDAYS': BirthdaysModal,
|
'BIRTHDAYS': BirthdaysModal,
|
||||||
'BOOST': BoostModal,
|
'BOOST': BoostModal,
|
||||||
|
'CAPTCHA': CaptchaModal,
|
||||||
'COMPARE_HISTORY': CompareHistoryModal,
|
'COMPARE_HISTORY': CompareHistoryModal,
|
||||||
'COMPONENT': ComponentModal,
|
'COMPONENT': ComponentModal,
|
||||||
'COMPOSE': ComposeModal,
|
'COMPOSE': ComposeModal,
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import useCaptcha from 'soapbox/api/hooks/captcha/useCaptcha';
|
||||||
|
import { Modal, Button, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
import { PuzzleCaptcha } from './components/puzzle';
|
||||||
|
|
||||||
|
interface ICaptchaModal {
|
||||||
|
onClose: (type?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CaptchaModal: React.FC<ICaptchaModal> = ({ onClose }) => {
|
||||||
|
const {
|
||||||
|
captcha,
|
||||||
|
loadCaptcha,
|
||||||
|
handleChangePosition,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
tryAgain,
|
||||||
|
yPosition,
|
||||||
|
xPosition,
|
||||||
|
} = useCaptcha();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<FormattedMessage id='nostr_signup.captcha_title' defaultMessage='Human Verification' />} width='sm'
|
||||||
|
>
|
||||||
|
<Stack justifyContent='center' alignItems='center' space={4}>
|
||||||
|
<Stack space={2} justifyContent='center' alignItems='center'>
|
||||||
|
<Text align='center'>
|
||||||
|
<FormattedMessage id='nostr_signup.captcha_instruction' defaultMessage='Complete the puzzle by dragging the puzzle piece to the correct position.' />
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className='relative flex min-h-[358px] min-w-[330px] items-center justify-center'>
|
||||||
|
{captcha ? <PuzzleCaptcha bg={captcha.bg} puzzle={captcha.puzzle} position={{ x: xPosition!, y: yPosition! }} onChange={handleChangePosition} /> : <Spinner size={40} withText={false} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Stack className='w-[330px]' space={2}>
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
theme='primary'
|
||||||
|
type='button'
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<FormattedMessage id='nostr_signup.captcha_check_button.checking' defaultMessage='Checking…' />
|
||||||
|
) : (tryAgain ?
|
||||||
|
<FormattedMessage id='nostr_signup.captcha_try_again_button' defaultMessage='Try again' /> :
|
||||||
|
<FormattedMessage id='nostr_signup.captcha_check_button' defaultMessage='Check' />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={loadCaptcha}>
|
||||||
|
<FormattedMessage id='nostr_signup.captcha_reset_button' defaultMessage='Reset puzzle' />
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CaptchaModal;
|
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
interface IPuzzleCaptcha {
|
||||||
|
bg: string;
|
||||||
|
puzzle: string;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
onChange(point: { x: number; y: number }): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PuzzleCaptcha: React.FC<IPuzzleCaptcha> = ({ bg, puzzle, position, onChange }) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const calculateNewPosition = (
|
||||||
|
clientX: number,
|
||||||
|
clientY: number,
|
||||||
|
elementWidth: number,
|
||||||
|
elementHeight: number,
|
||||||
|
dropArea: DOMRect,
|
||||||
|
) => {
|
||||||
|
const newX = Math.min(Math.max(clientX - dropArea.left - elementWidth / 2, 0), dropArea.width - elementWidth);
|
||||||
|
const newY = Math.min(Math.max(clientY - dropArea.top - elementHeight / 2, 0), dropArea.height - elementHeight);
|
||||||
|
return { x: newX, y: newY };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (e: React.PointerEvent<HTMLImageElement>) => {
|
||||||
|
if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;
|
||||||
|
const dropArea = ref.current?.getBoundingClientRect();
|
||||||
|
if (!dropArea) return;
|
||||||
|
|
||||||
|
const newPosition = calculateNewPosition(e.clientX, e.clientY, e.currentTarget.width, e.currentTarget.height, dropArea);
|
||||||
|
onChange(newPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchMove = (event: React.TouchEvent<HTMLImageElement>) => {
|
||||||
|
const touch = event.touches[0];
|
||||||
|
const dropArea = ref.current?.getBoundingClientRect();
|
||||||
|
if (!dropArea) return;
|
||||||
|
|
||||||
|
const newPosition = calculateNewPosition(touch.clientX, touch.clientY, 61, 61, dropArea);
|
||||||
|
onChange(newPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='drop-area' ref={ref} className='relative'>
|
||||||
|
<img
|
||||||
|
className='drop-shadow-black absolute z-[101] w-[61px] drop-shadow-2xl hover:cursor-grab'
|
||||||
|
src={puzzle}
|
||||||
|
alt=''
|
||||||
|
onPointerDown={(e) => e.currentTarget.setPointerCapture(e.pointerId)}
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
onPointerUp={(e) => e.currentTarget.releasePointerCapture(e.pointerId)}
|
||||||
|
onTouchMove={handleTouchMove}
|
||||||
|
style={{
|
||||||
|
filter: 'drop-shadow(2px 0 0 #fff) drop-shadow(0 2px 0 #fff) drop-shadow(-2px 0 0 #fff) drop-shadow(0 -2px 0 #fff)',
|
||||||
|
left: position.x,
|
||||||
|
top: position.y,
|
||||||
|
}}
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img src={bg} alt='' className='rounded-2xl' draggable={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -4,13 +4,14 @@ import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { fetchAccount } from 'soapbox/actions/accounts';
|
import { fetchAccount } from 'soapbox/actions/accounts';
|
||||||
import { logInNostr } from 'soapbox/actions/nostr';
|
import { logInNostr } from 'soapbox/actions/nostr';
|
||||||
import { startOnboarding } from 'soapbox/actions/onboarding';
|
import { closeSidebar } from 'soapbox/actions/sidebar';
|
||||||
import CopyableInput from 'soapbox/components/copyable-input';
|
import CopyableInput from 'soapbox/components/copyable-input';
|
||||||
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
||||||
import { Button, Stack, Modal, FormGroup, Text, Tooltip, HStack } from 'soapbox/components/ui';
|
import { Button, Stack, Modal, FormGroup, Text, Tooltip, HStack } from 'soapbox/components/ui';
|
||||||
import { useNostr } from 'soapbox/contexts/nostr-context';
|
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||||
import { NKeys } from 'soapbox/features/nostr/keys';
|
import { NKeys } from 'soapbox/features/nostr/keys';
|
||||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||||
|
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
|
||||||
import { download } from 'soapbox/utils/download';
|
import { download } from 'soapbox/utils/download';
|
||||||
import { slugify } from 'soapbox/utils/input';
|
import { slugify } from 'soapbox/utils/input';
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ interface IKeygenStep {
|
||||||
const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
const { relay } = useNostr();
|
const { relay } = useNostr();
|
||||||
|
|
||||||
const secretKey = useMemo(() => generateSecretKey(), []);
|
const secretKey = useMemo(() => generateSecretKey(), []);
|
||||||
|
@ -62,9 +64,13 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
await Promise.all(events.map((event) => relay?.event(event)));
|
await Promise.all(events.map((event) => relay?.event(event)));
|
||||||
|
|
||||||
await dispatch(logInNostr(pubkey));
|
await dispatch(logInNostr(pubkey));
|
||||||
dispatch(startOnboarding());
|
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
dispatch(closeSidebar());
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -82,7 +88,6 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|
||||||
<HStack space={6} justifyContent='center' >
|
<HStack space={6} justifyContent='center' >
|
||||||
<Button theme='secondary' size='lg' icon={require('@tabler/icons/outline/download.svg')} onClick={handleDownload}>
|
<Button theme='secondary' size='lg' icon={require('@tabler/icons/outline/download.svg')} onClick={handleDownload}>
|
||||||
<FormattedMessage id='nostr_signup.keygen.download_key_button' defaultMessage='Download key' />
|
<FormattedMessage id='nostr_signup.keygen.download_key_button' defaultMessage='Download key' />
|
||||||
|
|
|
@ -180,3 +180,4 @@ export const ZapPayRequestModal = lazy(() => import('soapbox/features/ui/compone
|
||||||
export const ZapInvoiceModal = lazy(() => import('soapbox/features/ui/components/modals/zap-invoice'));
|
export const ZapInvoiceModal = lazy(() => import('soapbox/features/ui/components/modals/zap-invoice'));
|
||||||
export const ZapsModal = lazy(() => import('soapbox/features/ui/components/modals/zaps-modal'));
|
export const ZapsModal = lazy(() => import('soapbox/features/ui/components/modals/zaps-modal'));
|
||||||
export const ZapSplitModal = lazy(() => import('soapbox/features/ui/components/modals/zap-split/zap-split-modal'));
|
export const ZapSplitModal = lazy(() => import('soapbox/features/ui/components/modals/zap-split/zap-split-modal'));
|
||||||
|
export const CaptchaModal = lazy(() => import('soapbox/features/ui/components/modals/captcha-modal/captcha-modal'));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense, useEffect } from 'react';
|
||||||
import { Toaster } from 'react-hot-toast';
|
import { Toaster } from 'react-hot-toast';
|
||||||
import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom';
|
import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom';
|
||||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||||
|
@ -35,11 +35,20 @@ const SoapboxMount = () => {
|
||||||
|
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
|
||||||
|
const showCaptcha = account && account?.source?.ditto.captcha_solved === false;
|
||||||
const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding);
|
const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding);
|
||||||
const showOnboarding = account && needsOnboarding;
|
const showOnboarding = account && needsOnboarding;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showCaptcha) {
|
||||||
|
dispatch(openModal('CAPTCHA'));
|
||||||
|
}
|
||||||
|
}, [showCaptcha]);
|
||||||
|
|
||||||
if (showOnboarding) {
|
if (showOnboarding) {
|
||||||
dispatch(openModal('ONBOARDING_FLOW'));
|
dispatch(openModal('ONBOARDING_FLOW'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { redirectRootNoLogin, gdpr } = soapboxConfig;
|
const { redirectRootNoLogin, gdpr } = soapboxConfig;
|
||||||
|
|
||||||
// @ts-ignore: I don't actually know what these should be, lol
|
// @ts-ignore: I don't actually know what these should be, lol
|
||||||
|
|
|
@ -1176,6 +1176,16 @@
|
||||||
"nostr_relays.title": "Relays",
|
"nostr_relays.title": "Relays",
|
||||||
"nostr_relays.write_only": "Write-only",
|
"nostr_relays.write_only": "Write-only",
|
||||||
"nostr_signin.siwe.welcome": "Welcome to {site_title}",
|
"nostr_signin.siwe.welcome": "Welcome to {site_title}",
|
||||||
|
"nostr_signup.captcha_check_button": "Check",
|
||||||
|
"nostr_signup.captcha_check_button.checking": "Checking…",
|
||||||
|
"nostr_signup.captcha_instruction": "Complete the puzzle by dragging the puzzle piece to the correct position.",
|
||||||
|
"nostr_signup.captcha_message.error": "It seems an error has occurred. Please try again. If the problem persists, please contact us.",
|
||||||
|
"nostr_signup.captcha_message.misbehaving": "It looks like we're experiencing issues with the {instance}. Please try again. If the error persists, try again later.",
|
||||||
|
"nostr_signup.captcha_message.sucess": "Incredible! You've successfully completed the captcha. Let's move on to the next step!",
|
||||||
|
"nostr_signup.captcha_message.wrong": "Oops! It looks like your captcha response was incorrect. Please try again.",
|
||||||
|
"nostr_signup.captcha_reset_button": "Reset puzzle",
|
||||||
|
"nostr_signup.captcha_title": "Human Verification",
|
||||||
|
"nostr_signup.captcha_try_again_button": "Try again",
|
||||||
"nostr_signup.has_key": "I already have a key",
|
"nostr_signup.has_key": "I already have a key",
|
||||||
"nostr_signup.key-add.key_button": "Add Key",
|
"nostr_signup.key-add.key_button": "Add Key",
|
||||||
"nostr_signup.key-add.title": "Import Key",
|
"nostr_signup.key-add.title": "Import Key",
|
||||||
|
|
|
@ -110,6 +110,9 @@ const baseAccountSchema = z.object({
|
||||||
nostr: z.object({
|
nostr: z.object({
|
||||||
nip05: z.string().optional().catch(undefined),
|
nip05: z.string().optional().catch(undefined),
|
||||||
}).optional().catch(undefined),
|
}).optional().catch(undefined),
|
||||||
|
ditto: coerceObject({
|
||||||
|
captcha_solved: z.boolean().catch(true),
|
||||||
|
}),
|
||||||
}).optional().catch(undefined),
|
}).optional().catch(undefined),
|
||||||
statuses_count: z.number().catch(0),
|
statuses_count: z.number().catch(0),
|
||||||
suspended: z.boolean().catch(false),
|
suspended: z.boolean().catch(false),
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const captchaSchema = z.object({
|
||||||
|
bg: z.string().catch(''),
|
||||||
|
created_at: z.string().catch(''),
|
||||||
|
expires_at: z.string().catch(''),
|
||||||
|
id: z.string().catch(''),
|
||||||
|
puzzle: z.string().catch(''),
|
||||||
|
type: z.string().catch(''),
|
||||||
|
});
|
||||||
|
|
||||||
|
type CaptchaData = z.infer<typeof captchaSchema>;
|
||||||
|
|
||||||
|
export { captchaSchema, type CaptchaData };
|
|
@ -2,8 +2,7 @@ import { NODE_ENV } from 'soapbox/build-config';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
|
|
||||||
import type { Account } from './schemas';
|
import type { Account } from './schemas';
|
||||||
import type { CaptureContext, UserFeedback } from '@sentry/types';
|
import type { CaptureContext, SendFeedbackParams } from '@sentry/types';
|
||||||
import type { SetOptional } from 'type-fest';
|
|
||||||
|
|
||||||
/** Start Sentry. */
|
/** Start Sentry. */
|
||||||
async function startSentry(dsn: string): Promise<void> {
|
async function startSentry(dsn: string): Promise<void> {
|
||||||
|
@ -13,7 +12,7 @@ async function startSentry(dsn: string): Promise<void> {
|
||||||
dsn,
|
dsn,
|
||||||
debug: false,
|
debug: false,
|
||||||
enabled: NODE_ENV === 'production',
|
enabled: NODE_ENV === 'production',
|
||||||
integrations: [new Sentry.BrowserTracing()],
|
integrations: [Sentry.browserTracingIntegration()],
|
||||||
|
|
||||||
// Filter events.
|
// Filter events.
|
||||||
// https://docs.sentry.io/platforms/javascript/configuration/filtering/
|
// https://docs.sentry.io/platforms/javascript/configuration/filtering/
|
||||||
|
@ -72,9 +71,9 @@ async function captureSentryException (
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Capture user feedback and report it to Sentry. */
|
/** Capture user feedback and report it to Sentry. */
|
||||||
async function captureSentryFeedback(feedback: SetOptional<UserFeedback, 'name' | 'email'>): Promise<void> {
|
async function captureSentryFeedback(feedback: SendFeedbackParams): Promise<void> {
|
||||||
const Sentry = await import('@sentry/react');
|
const Sentry = await import('@sentry/react');
|
||||||
Sentry.captureUserFeedback(feedback as UserFeedback);
|
Sentry.captureFeedback(feedback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
126
yarn.lock
126
yarn.lock
|
@ -2164,69 +2164,87 @@
|
||||||
"@noble/hashes" "~1.4.0"
|
"@noble/hashes" "~1.4.0"
|
||||||
"@scure/base" "~1.1.6"
|
"@scure/base" "~1.1.6"
|
||||||
|
|
||||||
"@sentry-internal/tracing@7.74.1":
|
"@sentry-internal/browser-utils@8.34.0":
|
||||||
version "7.74.1"
|
version "8.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.74.1.tgz#55ff387e61d2c9533a9a0d099d376332426c8e08"
|
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz#36a50d503ad4ad51fce22e80670f8fd6fd195a27"
|
||||||
integrity sha512-nNaiZreQxCitG2PzYPaC7XtyA9OMsETGYMKAtiK4p62/uTmeYbsBva9BoNx1XeiHRwbrVQYRMKQ9nV5e2jS4/A==
|
integrity sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/core" "7.74.1"
|
"@sentry/core" "8.34.0"
|
||||||
"@sentry/types" "7.74.1"
|
"@sentry/types" "8.34.0"
|
||||||
"@sentry/utils" "7.74.1"
|
"@sentry/utils" "8.34.0"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
|
||||||
|
|
||||||
"@sentry/browser@7.74.1", "@sentry/browser@^7.74.1":
|
"@sentry-internal/feedback@8.34.0":
|
||||||
version "7.74.1"
|
version "8.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.74.1.tgz#9302d440bbdcb018abd5fee5959dab4b2fe97383"
|
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.34.0.tgz#ff0db65c36f13665db99e3e22f2032bfdda98731"
|
||||||
integrity sha512-OYWNne/KO60lOvkIpIlJUyiJt/9j8DGI57thSDFEYSmmbNqMitczUTBOaEStouvHKyfchqLZm1CZfWKt+z0VOA==
|
integrity sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry-internal/tracing" "7.74.1"
|
"@sentry/core" "8.34.0"
|
||||||
"@sentry/core" "7.74.1"
|
"@sentry/types" "8.34.0"
|
||||||
"@sentry/replay" "7.74.1"
|
"@sentry/utils" "8.34.0"
|
||||||
"@sentry/types" "7.74.1"
|
|
||||||
"@sentry/utils" "7.74.1"
|
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
|
||||||
|
|
||||||
"@sentry/core@7.74.1":
|
"@sentry-internal/replay-canvas@8.34.0":
|
||||||
version "7.74.1"
|
version "8.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.74.1.tgz#9e33cf59b754a994e4054c47c74df1d3fbd30d3c"
|
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.34.0.tgz#10acadaef74e982dee2b9842a3eb6fec73f032ed"
|
||||||
integrity sha512-LvEhOSfdIvwkr+PdlrT/aA/iOLhkXrSkvjqAQyogE4ddCWeYfS0NoirxNt1EaxMBAWKhYZRqzkA7WA4LDLbzlA==
|
integrity sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/types" "7.74.1"
|
"@sentry-internal/replay" "8.34.0"
|
||||||
"@sentry/utils" "7.74.1"
|
"@sentry/core" "8.34.0"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
"@sentry/types" "8.34.0"
|
||||||
|
"@sentry/utils" "8.34.0"
|
||||||
|
|
||||||
"@sentry/react@^7.74.1":
|
"@sentry-internal/replay@8.34.0":
|
||||||
version "7.74.1"
|
version "8.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.74.1.tgz#43517f8e42cfab917ed909d2fce76b265febb9c7"
|
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.34.0.tgz#b730919a174cc5ae8a77f79fb24a5ffb18e44db5"
|
||||||
integrity sha512-16oTsNi2hl/S5AL/e5bo9DQZDwXPkX0nC8ajrpU0z2pH4cwjQZUZt/9Xq1+MKqDIEZkqDcMwpTmBptOvy1Pvkw==
|
integrity sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/browser" "7.74.1"
|
"@sentry-internal/browser-utils" "8.34.0"
|
||||||
"@sentry/types" "7.74.1"
|
"@sentry/core" "8.34.0"
|
||||||
"@sentry/utils" "7.74.1"
|
"@sentry/types" "8.34.0"
|
||||||
|
"@sentry/utils" "8.34.0"
|
||||||
|
|
||||||
|
"@sentry/browser@8.34.0", "@sentry/browser@^8.34.0":
|
||||||
|
version "8.34.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.34.0.tgz#d2dfc2dbbfa9132d5c3e951f0a4b467805bc4c75"
|
||||||
|
integrity sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ==
|
||||||
|
dependencies:
|
||||||
|
"@sentry-internal/browser-utils" "8.34.0"
|
||||||
|
"@sentry-internal/feedback" "8.34.0"
|
||||||
|
"@sentry-internal/replay" "8.34.0"
|
||||||
|
"@sentry-internal/replay-canvas" "8.34.0"
|
||||||
|
"@sentry/core" "8.34.0"
|
||||||
|
"@sentry/types" "8.34.0"
|
||||||
|
"@sentry/utils" "8.34.0"
|
||||||
|
|
||||||
|
"@sentry/core@8.34.0":
|
||||||
|
version "8.34.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.34.0.tgz#92efe1cc8ced843beee636c344e66086d8915563"
|
||||||
|
integrity sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/types" "8.34.0"
|
||||||
|
"@sentry/utils" "8.34.0"
|
||||||
|
|
||||||
|
"@sentry/react@^8.34.0":
|
||||||
|
version "8.34.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.34.0.tgz#f131d3b7168469617722474a3465a16cdcd77cb4"
|
||||||
|
integrity sha512-gIgzhj7h67C+Sdq2ul4fOSK142Gf0uV99bqHRdtIiUlXw9yjzZQY5TKTtzbOaevn7qBJ0xrRKtIRUbOBMl0clw==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/browser" "8.34.0"
|
||||||
|
"@sentry/core" "8.34.0"
|
||||||
|
"@sentry/types" "8.34.0"
|
||||||
|
"@sentry/utils" "8.34.0"
|
||||||
hoist-non-react-statics "^3.3.2"
|
hoist-non-react-statics "^3.3.2"
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
|
||||||
|
|
||||||
"@sentry/replay@7.74.1":
|
"@sentry/types@8.34.0", "@sentry/types@^8.34.0":
|
||||||
version "7.74.1"
|
version "8.34.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.74.1.tgz#dcb5040a3b0a9bda160b70cde5368ecbb4f0e782"
|
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.34.0.tgz#b02da72d1be67df5246aa9a97ca661ee71569372"
|
||||||
integrity sha512-qmbOl+jYdyhoHFbPp9WemKx8UojID5hVmuVLxNIP0ANqAwmE9OQEK9YFg2cf7L/TpKb1tqz0qLgi5MYIdcdpgQ==
|
integrity sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==
|
||||||
|
|
||||||
|
"@sentry/utils@8.34.0":
|
||||||
|
version "8.34.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.34.0.tgz#5ba543381a9de0ada1196df1fc5cde3b891de41e"
|
||||||
|
integrity sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/core" "7.74.1"
|
"@sentry/types" "8.34.0"
|
||||||
"@sentry/types" "7.74.1"
|
|
||||||
"@sentry/utils" "7.74.1"
|
|
||||||
|
|
||||||
"@sentry/types@7.74.1":
|
|
||||||
version "7.74.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.74.1.tgz#b6f9b1bd266254f1f8b55fbcc92fa649ba2100ed"
|
|
||||||
integrity sha512-2jIuPc+YKvXqZETwr2E8VYnsH1zsSUR/wkIvg1uTVeVNyoowJv+YsOtCdeGyL2AwiotUBSPKu7O1Lz0kq5rMOQ==
|
|
||||||
|
|
||||||
"@sentry/utils@7.74.1":
|
|
||||||
version "7.74.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.74.1.tgz#e9a8453c954d02ebed2fd3dbe7588483d8f6d3cb"
|
|
||||||
integrity sha512-qUsqufuHYcy5gFhLZslLxA5kcEOkkODITXW3c7D+x+8iP/AJqa8v8CeUCVNS7RetHCuIeWAbbTClC4c411EwQg==
|
|
||||||
dependencies:
|
|
||||||
"@sentry/types" "7.74.1"
|
|
||||||
tslib "^2.4.1 || ^1.9.3"
|
|
||||||
|
|
||||||
"@sindresorhus/is@^4.0.0":
|
"@sindresorhus/is@^4.0.0":
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
|
@ -8455,7 +8473,7 @@ tsconfig-paths@^3.14.2:
|
||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
tslib@2.6.2, tslib@^2.0.3, tslib@^2.4.0, "tslib@^2.4.1 || ^1.9.3":
|
tslib@2.6.2, tslib@^2.0.3, tslib@^2.4.0:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||||
|
|
Ładowanie…
Reference in New Issue