mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: auto-complete iframe challenges
This commit is contained in:
@@ -325,6 +325,70 @@ describe('ChallengeModal', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('auto-submits iframe challenges when the iframe posts a completion message', async () => {
|
||||||
|
const publication = createPublication();
|
||||||
|
testState.challenges = [
|
||||||
|
createStoredChallenge(
|
||||||
|
{
|
||||||
|
challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123',
|
||||||
|
type: 'url/iframe',
|
||||||
|
},
|
||||||
|
publication,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
await clickButton('Open');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new MessageEvent('message', {
|
||||||
|
data: {
|
||||||
|
type: 'challengeAnswer',
|
||||||
|
challengeAnswers: [''],
|
||||||
|
sessionId: 'session-123',
|
||||||
|
},
|
||||||
|
origin: 'https://spamblocker.bitsocial.net',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(publication.publishChallengeAnswers).toHaveBeenCalledWith(['']);
|
||||||
|
expect(testState.removeChallengeMock).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores iframe completion messages with the wrong session id', async () => {
|
||||||
|
const publication = createPublication();
|
||||||
|
testState.challenges = [
|
||||||
|
createStoredChallenge(
|
||||||
|
{
|
||||||
|
challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123',
|
||||||
|
type: 'url/iframe',
|
||||||
|
},
|
||||||
|
publication,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
await clickButton('Open');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new MessageEvent('message', {
|
||||||
|
data: {
|
||||||
|
type: 'challengeAnswer',
|
||||||
|
challengeAnswers: [''],
|
||||||
|
sessionId: 'session-999',
|
||||||
|
},
|
||||||
|
origin: 'https://spamblocker.bitsocial.net',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(publication.publishChallengeAnswers).not.toHaveBeenCalled();
|
||||||
|
expect(testState.removeChallengeMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('uses the shortened community address when shortCommunityAddress is unavailable', async () => {
|
it('uses the shortened community address when shortCommunityAddress is unavailable', async () => {
|
||||||
const longCommunityAddress = '12D3KooWS6yKc5N7o6JcAYHZpaQwAwyh1VddYatarU75Se3HXEeD';
|
const longCommunityAddress = '12D3KooWS6yKc5N7o6JcAYHZpaQwAwyh1VddYatarU75Se3HXEeD';
|
||||||
const publication = {
|
const publication = {
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ const getReadableIframeUrl = (challengeUrl: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getIframeSessionId = (challengeUrl: string) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(challengeUrl);
|
||||||
|
const match = url.pathname.match(/\/iframe\/([^/?#]+)/);
|
||||||
|
return match?.[1] ?? '';
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
interface IframeChallengeProps {
|
interface IframeChallengeProps {
|
||||||
challenge: string;
|
challenge: string;
|
||||||
shortCommunityAddress?: string;
|
shortCommunityAddress?: string;
|
||||||
@@ -44,17 +54,29 @@ interface IframeChallengeProps {
|
|||||||
readableUrl: string;
|
readableUrl: string;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onDone: () => void;
|
onDone: () => void;
|
||||||
|
onAutoComplete: (challengeAnswers: string[]) => void;
|
||||||
publicationDetails: React.ReactNode;
|
publicationDetails: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IframeChallenge = ({ challenge, shortCommunityAddress, communityAddress, readableUrl, onCancel, onDone, publicationDetails }: IframeChallengeProps) => {
|
const IframeChallenge = ({
|
||||||
|
challenge,
|
||||||
|
shortCommunityAddress,
|
||||||
|
communityAddress,
|
||||||
|
readableUrl,
|
||||||
|
onCancel,
|
||||||
|
onDone,
|
||||||
|
onAutoComplete,
|
||||||
|
publicationDetails,
|
||||||
|
}: IframeChallengeProps) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const [theme] = useTheme();
|
const [theme] = useTheme();
|
||||||
const [showIframeConfirmation, setShowIframeConfirmation] = useState(true);
|
const [showIframeConfirmation, setShowIframeConfirmation] = useState(true);
|
||||||
const [iframeUrlState, setIframeUrl] = useState('');
|
const [iframeUrlState, setIframeUrl] = useState('');
|
||||||
const [iframeOrigin, setIframeOrigin] = useState('');
|
const [iframeOrigin, setIframeOrigin] = useState('');
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
const handledAutoCompleteRef = useRef(false);
|
||||||
const displayCommunityAddress = shortCommunityAddress || (communityAddress ? getShortAddress(communityAddress) : '') || communityAddress || 'unknown board';
|
const displayCommunityAddress = shortCommunityAddress || (communityAddress ? getShortAddress(communityAddress) : '') || communityAddress || 'unknown board';
|
||||||
|
const expectedSessionId = getIframeSessionId(challenge);
|
||||||
|
|
||||||
const handleLoadIframe = useCallback(() => {
|
const handleLoadIframe = useCallback(() => {
|
||||||
const iframeUrl = challenge;
|
const iframeUrl = challenge;
|
||||||
@@ -110,6 +132,30 @@ const IframeChallenge = ({ challenge, shortCommunityAddress, communityAddress, r
|
|||||||
}
|
}
|
||||||
}, [iframeOrigin, iframeUrlState, sendThemeToIframe, showIframeConfirmation]);
|
}, [iframeOrigin, iframeUrlState, sendThemeToIframe, showIframeConfirmation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showIframeConfirmation || !iframeOrigin || !expectedSessionId) {
|
||||||
|
handledAutoCompleteRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
if (event.origin !== iframeOrigin) return;
|
||||||
|
if (handledAutoCompleteRef.current) return;
|
||||||
|
const data = event.data;
|
||||||
|
if (!data || typeof data !== 'object') return;
|
||||||
|
if ((data as { type?: string }).type !== 'challengeAnswer') return;
|
||||||
|
const challengeAnswers = (data as { challengeAnswers?: unknown }).challengeAnswers;
|
||||||
|
if (!Array.isArray(challengeAnswers)) return;
|
||||||
|
const sessionId = (data as { sessionId?: unknown }).sessionId;
|
||||||
|
if (sessionId !== expectedSessionId) return;
|
||||||
|
handledAutoCompleteRef.current = true;
|
||||||
|
onAutoComplete(challengeAnswers.filter((answer): answer is string => typeof answer === 'string'));
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
return () => window.removeEventListener('message', handleMessage);
|
||||||
|
}, [expectedSessionId, iframeOrigin, onAutoComplete, showIframeConfirmation]);
|
||||||
|
|
||||||
if (showIframeConfirmation) {
|
if (showIframeConfirmation) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -229,6 +275,15 @@ const Challenge = ({ challenge, closeModal, abandonModal }: ChallengeProps) => {
|
|||||||
closeModal();
|
closeModal();
|
||||||
}, [closeModal, publication]);
|
}, [closeModal, publication]);
|
||||||
|
|
||||||
|
const onIframeAutoComplete = useCallback(
|
||||||
|
(challengeAnswers: string[]) => {
|
||||||
|
if (!publication) return;
|
||||||
|
publication.publishChallengeAnswers(challengeAnswers);
|
||||||
|
closeModal();
|
||||||
|
},
|
||||||
|
[closeModal, publication],
|
||||||
|
);
|
||||||
|
|
||||||
const onEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const onEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key !== 'Enter') return;
|
if (e.key !== 'Enter') return;
|
||||||
if (!isValidAnswer(currentChallengeIndex)) return;
|
if (!isValidAnswer(currentChallengeIndex)) return;
|
||||||
@@ -323,6 +378,7 @@ const Challenge = ({ challenge, closeModal, abandonModal }: ChallengeProps) => {
|
|||||||
readableUrl={readableUrl}
|
readableUrl={readableUrl}
|
||||||
onCancel={abandonModal}
|
onCancel={abandonModal}
|
||||||
onDone={onIframeDone}
|
onDone={onIframeDone}
|
||||||
|
onAutoComplete={onIframeAutoComplete}
|
||||||
publicationDetails={publicationDetails}
|
publicationDetails={publicationDetails}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user