From e003ce972769060c8abca630230d89d33f0f194a Mon Sep 17 00:00:00 2001 From: danidfra Date: Wed, 9 Oct 2024 20:03:42 -0300 Subject: [PATCH 01/13] Created captcha schema --- src/schemas/captcha.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/schemas/captcha.ts diff --git a/src/schemas/captcha.ts b/src/schemas/captcha.ts new file mode 100644 index 000000000..3b6174d28 --- /dev/null +++ b/src/schemas/captcha.ts @@ -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; + +export { captchaSchema, type CaptchaData }; \ No newline at end of file From 3076a711d117832ff1081213a9b81d72f4c9fde6 Mon Sep 17 00:00:00 2001 From: danidfra Date: Wed, 9 Oct 2024 20:03:53 -0300 Subject: [PATCH 02/13] Add new messages --- src/locales/en.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/locales/en.json b/src/locales/en.json index 0accc3497..deeb4c619 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1176,6 +1176,16 @@ "nostr_relays.title": "Relays", "nostr_relays.write_only": "Write-only", "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.key-add.key_button": "Add Key", "nostr_signup.key-add.title": "Import Key", From e787da4e02b17e523ada85ec079df49a325b9cb3 Mon Sep 17 00:00:00 2001 From: danidfra Date: Wed, 9 Oct 2024 20:04:18 -0300 Subject: [PATCH 03/13] Implement Captcha --- src/features/ui/components/modal-root.tsx | 2 ++ .../modals/nostr-signup-modal/steps/keygen-step.tsx | 5 ++--- src/features/ui/util/async-components.ts | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index 890ad9c08..b7d27472d 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -46,6 +46,7 @@ import { ZapSplitModal, ZapInvoiceModal, ZapsModal, + CaptchaModal, } from 'soapbox/features/ui/util/async-components'; import ModalLoading from './modal-loading'; @@ -56,6 +57,7 @@ const MODAL_COMPONENTS: Record> = { 'ACTIONS': ActionsModal, 'BIRTHDAYS': BirthdaysModal, 'BOOST': BoostModal, + 'CAPTCHA': CaptchaModal, 'COMPARE_HISTORY': CompareHistoryModal, 'COMPONENT': ComponentModal, 'COMPOSE': ComposeModal, diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index f4692f05b..b923c8aaf 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -3,8 +3,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { fetchAccount } from 'soapbox/actions/accounts'; +import { openModal } from 'soapbox/actions/modals'; import { logInNostr } from 'soapbox/actions/nostr'; -import { startOnboarding } from 'soapbox/actions/onboarding'; import CopyableInput from 'soapbox/components/copyable-input'; import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal, FormGroup, Text, Tooltip, HStack } from 'soapbox/components/ui'; @@ -62,9 +62,9 @@ const KeygenStep: React.FC = ({ onClose }) => { await Promise.all(events.map((event) => relay?.event(event))); await dispatch(logInNostr(pubkey)); - dispatch(startOnboarding()); onClose(); + await dispatch(openModal('CAPTCHA')); }; return ( @@ -82,7 +82,6 @@ const KeygenStep: React.FC = ({ onClose }) => { - + + + + ); +}; + +export default Captcha; \ No newline at end of file diff --git a/src/features/ui/components/modals/captcha-modal/puzzle.tsx b/src/features/ui/components/modals/captcha-modal/puzzle.tsx new file mode 100644 index 000000000..1d44cc9d1 --- /dev/null +++ b/src/features/ui/components/modals/captcha-modal/puzzle.tsx @@ -0,0 +1,109 @@ +import React, { useRef, useState } 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 = ({ bg, puzzle, position, onChange }) => { + const ref = useRef(null); + const [touchOffsetX, setTouchOffsetX] = useState(0); + const [touchOffsetY, setTouchOffsetY] = useState(0); + + const handlePointerDown = (e: React.PointerEvent) => { + e.currentTarget.setPointerCapture(e.pointerId); + }; + + const handlePointerMove = (e: React.PointerEvent) => { + if (!e.currentTarget.hasPointerCapture(e.pointerId)) return; + + const rect = ref.current?.getBoundingClientRect(); + if (!rect) return; + + const newPosition = { + x: e.clientX - rect.left - e.currentTarget.width / 2, + y: e.clientY - rect.top - e.currentTarget.height / 2, + }; + + onChange(newPosition); + }; + + const handleTouchStart = (event: React.TouchEvent) => { + const imageElement = event.currentTarget.getBoundingClientRect(); + const touch = event.touches[0]; + + const offsetX = touch.clientX - imageElement.left; + const offsetY = touch.clientY - imageElement.top; + + setTouchOffsetX(offsetX); + setTouchOffsetY(offsetY); + }; + + + const handleTouchMove = (event: React.TouchEvent) => { + const touch = event.touches[0]; + + const dropArea = document.getElementById('drop-area')?.getBoundingClientRect(); + if (!dropArea) return; + + const newLeft = touch.clientX - dropArea.left - touchOffsetX; + const newTop = touch.clientY - dropArea.top - touchOffsetY; + + const imageWidth = 61; + const imageHeight = 61; + const limitedLeft = Math.min(Math.max(newLeft, 0), dropArea.width - imageWidth); + const limitedTop = Math.min(Math.max(newTop, 0), dropArea.height - imageHeight); + + onChange({ x: limitedLeft, y: limitedTop }); + }; + + const handlePointerUp = (e: React.PointerEvent) => { + e.currentTarget.releasePointerCapture(e.pointerId); + }; + + const handleTouchDrop = (event: React.TouchEvent) => { + event.preventDefault(); + + const dropArea = document.getElementById('drop-area')?.getBoundingClientRect(); + if (!dropArea) return; + + const touch = event.changedTouches[0]; + + const newLeft = touch.clientX - dropArea.left - touchOffsetX; + const newTop = touch.clientY - dropArea.top - touchOffsetY; + + const imageWidth = 61; + const imageHeight = 61; + + const limitedLeft = Math.min(Math.max(newLeft, 0), dropArea.width - imageWidth); + const limitedTop = Math.min(Math.max(newTop, 0), dropArea.height - imageHeight); + + onChange({ x: limitedLeft, y: limitedTop }); + + }; + + return ( +
+ + + +
+ ); +}; diff --git a/src/features/ui/components/modals/captcha-modal/useCaptcha.ts b/src/features/ui/components/modals/captcha-modal/useCaptcha.ts new file mode 100644 index 000000000..8f891e6c9 --- /dev/null +++ b/src/features/ui/components/modals/captcha-modal/useCaptcha.ts @@ -0,0 +1,114 @@ +import { AxiosError } from 'axios'; +import { useEffect, useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { closeModal } from 'soapbox/actions/modals'; +import { startOnboarding } from 'soapbox/actions/onboarding'; +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(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [tryAgain, setTryAgain] = useState(false); + const [yPosition, setYPosition] = useState(); + const [xPosition, setXPosition] = useState(); + + 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); + dispatch(startOnboarding()); + }); + } 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; \ No newline at end of file From e040c9f98174aedff6abfc3c786c76f4dfe7ee64 Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 14:13:46 -0300 Subject: [PATCH 05/13] Refactoring the puzzle and organizing files --- .../hooks/captcha}/useCaptcha.ts | 0 .../captcha-modal/components/puzzle.tsx | 64 ++++++++++ .../modals/captcha-modal/puzzle.tsx | 109 ------------------ 3 files changed, 64 insertions(+), 109 deletions(-) rename src/{features/ui/components/modals/captcha-modal => api/hooks/captcha}/useCaptcha.ts (100%) create mode 100644 src/features/ui/components/modals/captcha-modal/components/puzzle.tsx delete mode 100644 src/features/ui/components/modals/captcha-modal/puzzle.tsx diff --git a/src/features/ui/components/modals/captcha-modal/useCaptcha.ts b/src/api/hooks/captcha/useCaptcha.ts similarity index 100% rename from src/features/ui/components/modals/captcha-modal/useCaptcha.ts rename to src/api/hooks/captcha/useCaptcha.ts diff --git a/src/features/ui/components/modals/captcha-modal/components/puzzle.tsx b/src/features/ui/components/modals/captcha-modal/components/puzzle.tsx new file mode 100644 index 000000000..cb7303c6d --- /dev/null +++ b/src/features/ui/components/modals/captcha-modal/components/puzzle.tsx @@ -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 = ({ bg, puzzle, position, onChange }) => { + const ref = useRef(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) => { + 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) => { + 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 ( +
+ 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} + /> + + +
+ ); +}; diff --git a/src/features/ui/components/modals/captcha-modal/puzzle.tsx b/src/features/ui/components/modals/captcha-modal/puzzle.tsx deleted file mode 100644 index 1d44cc9d1..000000000 --- a/src/features/ui/components/modals/captcha-modal/puzzle.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React, { useRef, useState } 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 = ({ bg, puzzle, position, onChange }) => { - const ref = useRef(null); - const [touchOffsetX, setTouchOffsetX] = useState(0); - const [touchOffsetY, setTouchOffsetY] = useState(0); - - const handlePointerDown = (e: React.PointerEvent) => { - e.currentTarget.setPointerCapture(e.pointerId); - }; - - const handlePointerMove = (e: React.PointerEvent) => { - if (!e.currentTarget.hasPointerCapture(e.pointerId)) return; - - const rect = ref.current?.getBoundingClientRect(); - if (!rect) return; - - const newPosition = { - x: e.clientX - rect.left - e.currentTarget.width / 2, - y: e.clientY - rect.top - e.currentTarget.height / 2, - }; - - onChange(newPosition); - }; - - const handleTouchStart = (event: React.TouchEvent) => { - const imageElement = event.currentTarget.getBoundingClientRect(); - const touch = event.touches[0]; - - const offsetX = touch.clientX - imageElement.left; - const offsetY = touch.clientY - imageElement.top; - - setTouchOffsetX(offsetX); - setTouchOffsetY(offsetY); - }; - - - const handleTouchMove = (event: React.TouchEvent) => { - const touch = event.touches[0]; - - const dropArea = document.getElementById('drop-area')?.getBoundingClientRect(); - if (!dropArea) return; - - const newLeft = touch.clientX - dropArea.left - touchOffsetX; - const newTop = touch.clientY - dropArea.top - touchOffsetY; - - const imageWidth = 61; - const imageHeight = 61; - const limitedLeft = Math.min(Math.max(newLeft, 0), dropArea.width - imageWidth); - const limitedTop = Math.min(Math.max(newTop, 0), dropArea.height - imageHeight); - - onChange({ x: limitedLeft, y: limitedTop }); - }; - - const handlePointerUp = (e: React.PointerEvent) => { - e.currentTarget.releasePointerCapture(e.pointerId); - }; - - const handleTouchDrop = (event: React.TouchEvent) => { - event.preventDefault(); - - const dropArea = document.getElementById('drop-area')?.getBoundingClientRect(); - if (!dropArea) return; - - const touch = event.changedTouches[0]; - - const newLeft = touch.clientX - dropArea.left - touchOffsetX; - const newTop = touch.clientY - dropArea.top - touchOffsetY; - - const imageWidth = 61; - const imageHeight = 61; - - const limitedLeft = Math.min(Math.max(newLeft, 0), dropArea.width - imageWidth); - const limitedTop = Math.min(Math.max(newTop, 0), dropArea.height - imageHeight); - - onChange({ x: limitedLeft, y: limitedTop }); - - }; - - return ( -
- - - -
- ); -}; From 41dde9448f0c686dfaab287aac162db317fce6df Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 14:27:21 -0300 Subject: [PATCH 06/13] Refactoring and improving the onboarding trigger --- src/components/modal-root.tsx | 4 ++++ .../components/modals/captcha-modal/captcha-modal.tsx | 10 +++++++++- .../ui/components/modals/captcha-modal/captcha.tsx | 4 ++-- .../modals/nostr-signup-modal/steps/keygen-step.tsx | 8 ++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/modal-root.tsx b/src/components/modal-root.tsx index 84b7830e1..5afc25d4a 100644 --- a/src/components/modal-root.tsx +++ b/src/components/modal-root.tsx @@ -6,6 +6,7 @@ import { useHistory } from 'react-router-dom'; import { cancelReplyCompose } from 'soapbox/actions/compose'; import { cancelEventCompose } from 'soapbox/actions/events'; import { openModal, closeModal } from 'soapbox/actions/modals'; +import { startOnboarding } from 'soapbox/actions/onboarding'; import { useAppDispatch, usePrevious } from 'soapbox/hooks'; import type { ModalType } from 'soapbox/features/ui/components/modal-root'; @@ -111,6 +112,9 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) })); } else if ((hasComposeContent || hasEventComposeContent) && type === 'CONFIRM') { dispatch(closeModal('CONFIRM')); + } else if (type === 'CAPTCHA') { + dispatch(startOnboarding()); + onClose(); } else { onClose(); } diff --git a/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx b/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx index 9b1fcbc63..b6d010ad4 100644 --- a/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx +++ b/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { startOnboarding } from 'soapbox/actions/onboarding'; import { Modal, Stack } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; import Captcha from './captcha'; @@ -10,8 +12,14 @@ interface ICaptchaModal { } const CaptchaModal: React.FC = ({ onClose }) => { + const dispatch = useAppDispatch(); return ( - } onClose={onClose} width='sm'> + } onClose={() => { + onClose(); + dispatch(startOnboarding()); + }} width='sm' + > diff --git a/src/features/ui/components/modals/captcha-modal/captcha.tsx b/src/features/ui/components/modals/captcha-modal/captcha.tsx index bc85d3f88..a11e01431 100644 --- a/src/features/ui/components/modals/captcha-modal/captcha.tsx +++ b/src/features/ui/components/modals/captcha-modal/captcha.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import useCaptcha from 'soapbox/api/hooks/captcha/useCaptcha'; import { Button, Spinner, Stack, Text } from 'soapbox/components/ui'; -import { PuzzleCaptcha } from './puzzle'; -import useCaptcha from './useCaptcha'; +import { PuzzleCaptcha } from './components/puzzle'; const Captcha: React.FC = () => { const { diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index b923c8aaf..9c90a8a98 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -5,12 +5,14 @@ import { FormattedMessage } from 'react-intl'; import { fetchAccount } from 'soapbox/actions/accounts'; import { openModal } from 'soapbox/actions/modals'; import { logInNostr } from 'soapbox/actions/nostr'; +import { closeSidebar } from 'soapbox/actions/sidebar'; import CopyableInput from 'soapbox/components/copyable-input'; import EmojiGraphic from 'soapbox/components/emoji-graphic'; import { Button, Stack, Modal, FormGroup, Text, Tooltip, HStack } from 'soapbox/components/ui'; import { useNostr } from 'soapbox/contexts/nostr-context'; import { NKeys } from 'soapbox/features/nostr/keys'; import { useAppDispatch, useInstance } from 'soapbox/hooks'; +import { useIsMobile } from 'soapbox/hooks/useIsMobile'; import { download } from 'soapbox/utils/download'; import { slugify } from 'soapbox/utils/input'; @@ -21,6 +23,7 @@ interface IKeygenStep { const KeygenStep: React.FC = ({ onClose }) => { const instance = useInstance(); const dispatch = useAppDispatch(); + const isMobile = useIsMobile(); const { relay } = useNostr(); const secretKey = useMemo(() => generateSecretKey(), []); @@ -64,6 +67,11 @@ const KeygenStep: React.FC = ({ onClose }) => { await dispatch(logInNostr(pubkey)); onClose(); + + if (isMobile) { + dispatch(closeSidebar()); + } + await dispatch(openModal('CAPTCHA')); }; From 162b0042ed37f41cfbc99e3decdf48260cceed65 Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 17:26:15 -0300 Subject: [PATCH 07/13] Update account schema --- src/schemas/account.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schemas/account.ts b/src/schemas/account.ts index 1a6944386..86e5d7985 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -110,6 +110,8 @@ const baseAccountSchema = z.object({ nostr: z.object({ nip05: z.string().optional().catch(undefined), }).optional().catch(undefined), + ditto: z.object({ + captcha_solved: z.boolean().catch(true) }), }).optional().catch(undefined), statuses_count: z.number().catch(0), suspended: z.boolean().catch(false), From 3f6d589e9319814ded11d23e4fd374b6c766f8f7 Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 17:28:56 -0300 Subject: [PATCH 08/13] Using captcha_solved --- src/api/hooks/captcha/useCaptcha.ts | 2 -- .../ui/components/modals/captcha-modal/captcha-modal.tsx | 8 +------- .../modals/nostr-signup-modal/steps/keygen-step.tsx | 2 -- src/init/soapbox-mount.tsx | 7 +++++++ 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/api/hooks/captcha/useCaptcha.ts b/src/api/hooks/captcha/useCaptcha.ts index 8f891e6c9..8085376db 100644 --- a/src/api/hooks/captcha/useCaptcha.ts +++ b/src/api/hooks/captcha/useCaptcha.ts @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { closeModal } from 'soapbox/actions/modals'; -import { startOnboarding } from 'soapbox/actions/onboarding'; import { useApi, useAppDispatch, useInstance } from 'soapbox/hooks'; import { captchaSchema, type CaptchaData } from 'soapbox/schemas/captcha'; import toast from 'soapbox/toast'; @@ -73,7 +72,6 @@ const useCaptcha = () => { dispatch(closeModal('CAPTCHA')); toast.success(messages.sucessMessage); - dispatch(startOnboarding()); }); } catch (e) { setTryAgain(true); diff --git a/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx b/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx index b6d010ad4..b84e78c04 100644 --- a/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx +++ b/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx @@ -1,9 +1,7 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { startOnboarding } from 'soapbox/actions/onboarding'; import { Modal, Stack } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; import Captcha from './captcha'; @@ -12,13 +10,9 @@ interface ICaptchaModal { } const CaptchaModal: React.FC = ({ onClose }) => { - const dispatch = useAppDispatch(); return ( } onClose={() => { - onClose(); - dispatch(startOnboarding()); - }} width='sm' + title={} width='sm' > diff --git a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx index 9c90a8a98..1d6274ce8 100644 --- a/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx +++ b/src/features/ui/components/modals/nostr-signup-modal/steps/keygen-step.tsx @@ -3,7 +3,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { fetchAccount } from 'soapbox/actions/accounts'; -import { openModal } from 'soapbox/actions/modals'; import { logInNostr } from 'soapbox/actions/nostr'; import { closeSidebar } from 'soapbox/actions/sidebar'; import CopyableInput from 'soapbox/components/copyable-input'; @@ -72,7 +71,6 @@ const KeygenStep: React.FC = ({ onClose }) => { dispatch(closeSidebar()); } - await dispatch(openModal('CAPTCHA')); }; return ( diff --git a/src/init/soapbox-mount.tsx b/src/init/soapbox-mount.tsx index 1b7ba320e..49bed38cc 100644 --- a/src/init/soapbox-mount.tsx +++ b/src/init/soapbox-mount.tsx @@ -35,11 +35,18 @@ const SoapboxMount = () => { const soapboxConfig = useSoapboxConfig(); + const showCaptcha = account && !account?.source?.ditto.captcha_solved; const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding); const showOnboarding = account && needsOnboarding; + + if (showCaptcha) { + dispatch(openModal('CAPTCHA')); + } + if (showOnboarding) { dispatch(openModal('ONBOARDING_FLOW')); } + const { redirectRootNoLogin, gdpr } = soapboxConfig; // @ts-ignore: I don't actually know what these should be, lol From ea19016ca3e8de690f6ce67bef9572775625ec8e Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 17:36:10 -0300 Subject: [PATCH 09/13] Merged captcha and captcha-modal --- .../modals/captcha-modal/captcha-modal.tsx | 46 ++++++++++++++-- .../modals/captcha-modal/captcha.tsx | 54 ------------------- 2 files changed, 43 insertions(+), 57 deletions(-) delete mode 100644 src/features/ui/components/modals/captcha-modal/captcha.tsx diff --git a/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx b/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx index b84e78c04..5dac02d2d 100644 --- a/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx +++ b/src/features/ui/components/modals/captcha-modal/captcha-modal.tsx @@ -1,21 +1,61 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { Modal, Stack } from 'soapbox/components/ui'; +import useCaptcha from 'soapbox/api/hooks/captcha/useCaptcha'; +import { Modal, Button, Spinner, Stack, Text } from 'soapbox/components/ui'; -import Captcha from './captcha'; +import { PuzzleCaptcha } from './components/puzzle'; interface ICaptchaModal { onClose: (type?: string) => void; } const CaptchaModal: React.FC = ({ onClose }) => { + const { + captcha, + loadCaptcha, + handleChangePosition, + handleSubmit, + isSubmitting, + tryAgain, + yPosition, + xPosition, + } = useCaptcha(); + return ( } width='sm' > - + + + + + +
+ {captcha ? : } +
+ + + + + +
); diff --git a/src/features/ui/components/modals/captcha-modal/captcha.tsx b/src/features/ui/components/modals/captcha-modal/captcha.tsx deleted file mode 100644 index a11e01431..000000000 --- a/src/features/ui/components/modals/captcha-modal/captcha.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; - -import useCaptcha from 'soapbox/api/hooks/captcha/useCaptcha'; -import { Button, Spinner, Stack, Text } from 'soapbox/components/ui'; - -import { PuzzleCaptcha } from './components/puzzle'; - -const Captcha: React.FC = () => { - const { - captcha, - loadCaptcha, - handleChangePosition, - handleSubmit, - isSubmitting, - tryAgain, - yPosition, - xPosition, - } = useCaptcha(); - - return ( - - - - - -
- {captcha ? : } -
- - - - - -
- ); -}; - -export default Captcha; \ No newline at end of file From bf16faccf099acdcd0c2556deab30cb1ba10183b Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 17:40:54 -0300 Subject: [PATCH 10/13] Update the captcha modal to not close on outside clicks --- src/components/modal-root.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/modal-root.tsx b/src/components/modal-root.tsx index 5afc25d4a..0612b90ad 100644 --- a/src/components/modal-root.tsx +++ b/src/components/modal-root.tsx @@ -6,7 +6,6 @@ import { useHistory } from 'react-router-dom'; import { cancelReplyCompose } from 'soapbox/actions/compose'; import { cancelEventCompose } from 'soapbox/actions/events'; import { openModal, closeModal } from 'soapbox/actions/modals'; -import { startOnboarding } from 'soapbox/actions/onboarding'; import { useAppDispatch, usePrevious } from 'soapbox/hooks'; import type { ModalType } from 'soapbox/features/ui/components/modal-root'; @@ -113,8 +112,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) } else if ((hasComposeContent || hasEventComposeContent) && type === 'CONFIRM') { dispatch(closeModal('CONFIRM')); } else if (type === 'CAPTCHA') { - dispatch(startOnboarding()); - onClose(); + return; } else { onClose(); } From 130d4b149b37eb2a88a2f42c83c46c447820ac3c Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 17:46:38 -0300 Subject: [PATCH 11/13] Add useEffect on showCaptcha --- src/init/soapbox-mount.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/init/soapbox-mount.tsx b/src/init/soapbox-mount.tsx index 49bed38cc..ad187beec 100644 --- a/src/init/soapbox-mount.tsx +++ b/src/init/soapbox-mount.tsx @@ -1,4 +1,4 @@ -import React, { Suspense } from 'react'; +import React, { Suspense, useEffect } from 'react'; import { Toaster } from 'react-hot-toast'; import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom'; import { CompatRouter } from 'react-router-dom-v5-compat'; @@ -39,9 +39,11 @@ const SoapboxMount = () => { const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding); const showOnboarding = account && needsOnboarding; - if (showCaptcha) { - dispatch(openModal('CAPTCHA')); - } + useEffect(() => { + if (showCaptcha) { + dispatch(openModal('CAPTCHA')); + } + }, [showCaptcha]); if (showOnboarding) { dispatch(openModal('ONBOARDING_FLOW')); From 0d58e68911e42473e715d7375c1e383f4aa74806 Mon Sep 17 00:00:00 2001 From: danidfra Date: Thu, 10 Oct 2024 17:50:01 -0300 Subject: [PATCH 12/13] Last changes --- src/init/soapbox-mount.tsx | 2 +- src/schemas/account.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/init/soapbox-mount.tsx b/src/init/soapbox-mount.tsx index ad187beec..d91c71c3e 100644 --- a/src/init/soapbox-mount.tsx +++ b/src/init/soapbox-mount.tsx @@ -35,7 +35,7 @@ const SoapboxMount = () => { const soapboxConfig = useSoapboxConfig(); - const showCaptcha = account && !account?.source?.ditto.captcha_solved; + const showCaptcha = account && account?.source?.ditto.captcha_solved === false; const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding); const showOnboarding = account && needsOnboarding; diff --git a/src/schemas/account.ts b/src/schemas/account.ts index 86e5d7985..702b631b0 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -110,8 +110,9 @@ const baseAccountSchema = z.object({ nostr: z.object({ nip05: z.string().optional().catch(undefined), }).optional().catch(undefined), - ditto: z.object({ - captcha_solved: z.boolean().catch(true) }), + ditto: coerceObject({ + captcha_solved: z.boolean().catch(true), + }), }).optional().catch(undefined), statuses_count: z.number().catch(0), suspended: z.boolean().catch(false), From e8005508666d8acee8b57544855a08e0ef959c10 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 10 Oct 2024 18:18:35 -0500 Subject: [PATCH 13/13] Upgrade Sentry to v8.34 --- package.json | 5 +- src/components/sentry-feedback-form.tsx | 4 +- src/sentry.ts | 9 +- yarn.lock | 126 ++++++++++++++---------- 4 files changed, 81 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 8da894683..4574cd63d 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,9 @@ "@reach/rect": "^0.18.0", "@reach/tabs": "^0.18.0", "@reduxjs/toolkit": "^2.0.1", - "@sentry/browser": "^7.74.1", - "@sentry/react": "^7.74.1", + "@sentry/browser": "^8.34.0", + "@sentry/react": "^8.34.0", + "@sentry/types": "^8.34.0", "@soapbox.pub/wasmboy": "^0.8.0", "@soapbox/weblock": "npm:@jsr/soapbox__weblock", "@tabler/icons": "^3.1.0", diff --git a/src/components/sentry-feedback-form.tsx b/src/components/sentry-feedback-form.tsx index 1f8ba2a88..292928aaf 100644 --- a/src/components/sentry-feedback-form.tsx +++ b/src/components/sentry-feedback-form.tsx @@ -27,8 +27,8 @@ const SentryFeedbackForm: React.FC = ({ eventId }) => { await captureSentryFeedback({ name: account?.acct, - event_id: eventId, - comments: feedback, + associatedEventId: eventId, + message: feedback, }).catch(console.error); setIsSubmitted(true); diff --git a/src/sentry.ts b/src/sentry.ts index 89f1495f9..9b053d92e 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -2,8 +2,7 @@ import { NODE_ENV } from 'soapbox/build-config'; import sourceCode from 'soapbox/utils/code'; import type { Account } from './schemas'; -import type { CaptureContext, UserFeedback } from '@sentry/types'; -import type { SetOptional } from 'type-fest'; +import type { CaptureContext, SendFeedbackParams } from '@sentry/types'; /** Start Sentry. */ async function startSentry(dsn: string): Promise { @@ -13,7 +12,7 @@ async function startSentry(dsn: string): Promise { dsn, debug: false, enabled: NODE_ENV === 'production', - integrations: [new Sentry.BrowserTracing()], + integrations: [Sentry.browserTracingIntegration()], // Filter events. // https://docs.sentry.io/platforms/javascript/configuration/filtering/ @@ -72,9 +71,9 @@ async function captureSentryException ( } /** Capture user feedback and report it to Sentry. */ -async function captureSentryFeedback(feedback: SetOptional): Promise { +async function captureSentryFeedback(feedback: SendFeedbackParams): Promise { const Sentry = await import('@sentry/react'); - Sentry.captureUserFeedback(feedback as UserFeedback); + Sentry.captureFeedback(feedback); } export { diff --git a/yarn.lock b/yarn.lock index dc55a253c..2c6d98610 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2164,69 +2164,87 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@sentry-internal/tracing@7.74.1": - version "7.74.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.74.1.tgz#55ff387e61d2c9533a9a0d099d376332426c8e08" - integrity sha512-nNaiZreQxCitG2PzYPaC7XtyA9OMsETGYMKAtiK4p62/uTmeYbsBva9BoNx1XeiHRwbrVQYRMKQ9nV5e2jS4/A== +"@sentry-internal/browser-utils@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz#36a50d503ad4ad51fce22e80670f8fd6fd195a27" + integrity sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ== dependencies: - "@sentry/core" "7.74.1" - "@sentry/types" "7.74.1" - "@sentry/utils" "7.74.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry/core" "8.34.0" + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" -"@sentry/browser@7.74.1", "@sentry/browser@^7.74.1": - version "7.74.1" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.74.1.tgz#9302d440bbdcb018abd5fee5959dab4b2fe97383" - integrity sha512-OYWNne/KO60lOvkIpIlJUyiJt/9j8DGI57thSDFEYSmmbNqMitczUTBOaEStouvHKyfchqLZm1CZfWKt+z0VOA== +"@sentry-internal/feedback@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.34.0.tgz#ff0db65c36f13665db99e3e22f2032bfdda98731" + integrity sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw== dependencies: - "@sentry-internal/tracing" "7.74.1" - "@sentry/core" "7.74.1" - "@sentry/replay" "7.74.1" - "@sentry/types" "7.74.1" - "@sentry/utils" "7.74.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry/core" "8.34.0" + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" -"@sentry/core@7.74.1": - version "7.74.1" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.74.1.tgz#9e33cf59b754a994e4054c47c74df1d3fbd30d3c" - integrity sha512-LvEhOSfdIvwkr+PdlrT/aA/iOLhkXrSkvjqAQyogE4ddCWeYfS0NoirxNt1EaxMBAWKhYZRqzkA7WA4LDLbzlA== +"@sentry-internal/replay-canvas@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.34.0.tgz#10acadaef74e982dee2b9842a3eb6fec73f032ed" + integrity sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg== dependencies: - "@sentry/types" "7.74.1" - "@sentry/utils" "7.74.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry-internal/replay" "8.34.0" + "@sentry/core" "8.34.0" + "@sentry/types" "8.34.0" + "@sentry/utils" "8.34.0" -"@sentry/react@^7.74.1": - version "7.74.1" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.74.1.tgz#43517f8e42cfab917ed909d2fce76b265febb9c7" - integrity sha512-16oTsNi2hl/S5AL/e5bo9DQZDwXPkX0nC8ajrpU0z2pH4cwjQZUZt/9Xq1+MKqDIEZkqDcMwpTmBptOvy1Pvkw== +"@sentry-internal/replay@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.34.0.tgz#b730919a174cc5ae8a77f79fb24a5ffb18e44db5" + integrity sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw== dependencies: - "@sentry/browser" "7.74.1" - "@sentry/types" "7.74.1" - "@sentry/utils" "7.74.1" + "@sentry-internal/browser-utils" "8.34.0" + "@sentry/core" "8.34.0" + "@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" - tslib "^2.4.1 || ^1.9.3" -"@sentry/replay@7.74.1": - version "7.74.1" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.74.1.tgz#dcb5040a3b0a9bda160b70cde5368ecbb4f0e782" - integrity sha512-qmbOl+jYdyhoHFbPp9WemKx8UojID5hVmuVLxNIP0ANqAwmE9OQEK9YFg2cf7L/TpKb1tqz0qLgi5MYIdcdpgQ== +"@sentry/types@8.34.0", "@sentry/types@^8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.34.0.tgz#b02da72d1be67df5246aa9a97ca661ee71569372" + 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: - "@sentry/core" "7.74.1" - "@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" + "@sentry/types" "8.34.0" "@sindresorhus/is@^4.0.0": version "4.6.0" @@ -8450,7 +8468,7 @@ tsconfig-paths@^3.14.2: minimist "^1.2.6" 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" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==