import { useRef, useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Challenge as ChallengeType, useAccount, useComment } from '@bitsocialnet/bitsocial-react-hooks'; import { getPublicationPreview, getPublicationType, getVotePreview } from '../../lib/utils/challenge-utils'; import useIsMobile from '../../hooks/use-is-mobile'; import useChallengesStore from '../../stores/use-challenges-store'; import useTheme from '../../hooks/use-theme'; import styles from './challenge-modal.module.css'; import capitalize from 'lodash/capitalize'; import { useSpring, animated } from '@react-spring/web'; import { useDrag } from '@use-gesture/react'; import getShortAddress from '../../lib/get-short-address'; const useParentAddress = (parentCid?: string) => { const parentComment = useComment({ commentCid: parentCid, onlyIfCached: true }); return parentComment?.author?.shortAddress; }; interface ChallengeProps { challenge: ChallengeType; closeModal: () => void; abandonModal: () => void; } const TextChallenge = ({ challenge }: { challenge: string }) =>
{challenge}
; const ImageChallenge = ({ challenge }: { challenge: string }) => ; const isLocalIframeHostname = (hostname: string) => hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]' || hostname.endsWith('.localhost'); const getReadableIframeUrl = (challengeUrl: string) => { try { const url = new URL(challengeUrl); return url.host || url.hostname; } catch { return ''; } }; interface IframeChallengeProps { challenge: string; shortCommunityAddress?: string; communityAddress?: string; readableUrl: string; onCancel: () => void; onDone: () => void; publicationDetails: React.ReactNode; } const IframeChallenge = ({ challenge, shortCommunityAddress, communityAddress, readableUrl, onCancel, onDone, publicationDetails }: IframeChallengeProps) => { const account = useAccount(); const [theme] = useTheme(); const [showIframeConfirmation, setShowIframeConfirmation] = useState(true); const [iframeUrlState, setIframeUrl] = useState(''); const [iframeOrigin, setIframeOrigin] = useState(''); const iframeRef = useRef(null); const displayCommunityAddress = shortCommunityAddress || (communityAddress ? getShortAddress(communityAddress) : '') || communityAddress || 'unknown board'; const handleLoadIframe = useCallback(() => { const iframeUrl = challenge; if (!iframeUrl) return; const rawUserAddress = account?.author?.address?.trim(); const requiresUserAddress = iframeUrl.includes('{userAddress}'); if (requiresUserAddress && !rawUserAddress) { alert('Error: Unable to load challenge without your address. Please sign in and try again.'); return; } const encodedAddress = rawUserAddress ? encodeURIComponent(rawUserAddress) : undefined; const replacedUrl = requiresUserAddress && encodedAddress ? iframeUrl.replace(/\{userAddress\}/g, encodedAddress) : iframeUrl; try { const validatedUrl = new URL(replacedUrl); const isHttps = validatedUrl.protocol === 'https:'; const isLocalHttp = validatedUrl.protocol === 'http:' && isLocalIframeHostname(validatedUrl.hostname); if (!isHttps && !isLocalHttp) { throw new Error('Only HTTPS iframe challenges or localhost HTTP challenges are supported'); } validatedUrl.pathname = validatedUrl.pathname.replace(/\/{2,}/g, '/'); validatedUrl.searchParams.set('theme', theme); const finalUrl = validatedUrl.toString(); setIframeUrl(finalUrl); setIframeOrigin(validatedUrl.origin); setShowIframeConfirmation(false); } catch (error) { console.error('Invalid iframe challenge URL', { error }); alert('Error: Invalid URL for authentication challenge'); onCancel(); } }, [account, challenge, onCancel, theme]); const sendThemeToIframe = useCallback(() => { if (!iframeRef.current || !iframeOrigin) return; try { iframeRef.current.contentWindow?.postMessage({ type: 'plebbit-theme', theme, source: 'plebbit-5chan' }, iframeOrigin); } catch (error) { console.warn('Could not send theme to iframe:', error); } }, [iframeOrigin, theme]); const handleIframeLoad = () => { sendThemeToIframe(); }; useEffect(() => { if (iframeRef.current && iframeUrlState && iframeOrigin && !showIframeConfirmation) { sendThemeToIframe(); } }, [iframeOrigin, iframeUrlState, sendThemeToIframe, showIframeConfirmation]); if (showIframeConfirmation) { return ( <> {publicationDetails}
{displayCommunityAddress} wants to open {readableUrl || 'an external site'}
); } return ( <>