mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(challenge modal): avoid native iframe consent on WebKit
This commit is contained in:
@@ -15,6 +15,7 @@ const testState = vi.hoisted(() => ({
|
|||||||
address: '0xabc123',
|
address: '0xabc123',
|
||||||
},
|
},
|
||||||
} as Record<string, any>,
|
} as Record<string, any>,
|
||||||
|
capacitorPlatform: 'web',
|
||||||
challenges: [] as Array<{ challenge: any; id: number }>,
|
challenges: [] as Array<{ challenge: any; id: number }>,
|
||||||
commentsByCid: {} as Record<string, { author?: { shortAddress?: string } }>,
|
commentsByCid: {} as Record<string, { author?: { shortAddress?: string } }>,
|
||||||
publicationPreview: 'preview body',
|
publicationPreview: 'preview body',
|
||||||
@@ -44,6 +45,12 @@ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
|
|||||||
useComment: ({ commentCid }: { commentCid?: string }) => (commentCid ? testState.commentsByCid[commentCid] : undefined),
|
useComment: ({ commentCid }: { commentCid?: string }) => (commentCid ? testState.commentsByCid[commentCid] : undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('@capacitor/core', () => ({
|
||||||
|
Capacitor: {
|
||||||
|
getPlatform: () => testState.capacitorPlatform,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../lib/utils/challenge-utils', () => ({
|
vi.mock('../../../lib/utils/challenge-utils', () => ({
|
||||||
getPublicationPreview: () => testState.publicationPreview,
|
getPublicationPreview: () => testState.publicationPreview,
|
||||||
getPublicationType: () => testState.publicationType,
|
getPublicationType: () => testState.publicationType,
|
||||||
@@ -139,6 +146,13 @@ const clickButton = async (text: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setNavigatorValue = (key: keyof Navigator, value: unknown) => {
|
||||||
|
Object.defineProperty(window.navigator, key, {
|
||||||
|
configurable: true,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
describe('ChallengeModal', () => {
|
describe('ChallengeModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -148,6 +162,7 @@ describe('ChallengeModal', () => {
|
|||||||
address: '0xabc123',
|
address: '0xabc123',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
testState.capacitorPlatform = 'web';
|
||||||
testState.challenges = [];
|
testState.challenges = [];
|
||||||
testState.commentsByCid = {
|
testState.commentsByCid = {
|
||||||
'parent-1': {
|
'parent-1': {
|
||||||
@@ -162,6 +177,10 @@ describe('ChallengeModal', () => {
|
|||||||
testState.springStartMock.mockReset();
|
testState.springStartMock.mockReset();
|
||||||
testState.theme = 'dark';
|
testState.theme = 'dark';
|
||||||
testState.votePreview = 'upvote';
|
testState.votePreview = 'upvote';
|
||||||
|
setNavigatorValue('maxTouchPoints', 0);
|
||||||
|
setNavigatorValue('platform', 'MacIntel');
|
||||||
|
setNavigatorValue('userAgent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125 Safari/537.36');
|
||||||
|
setNavigatorValue('vendor', 'Google Inc.');
|
||||||
alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => undefined);
|
alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => undefined);
|
||||||
confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true);
|
confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true);
|
||||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||||
@@ -465,6 +484,113 @@ describe('ChallengeModal', () => {
|
|||||||
expect(container.querySelector('iframe')).toBeNull();
|
expect(container.querySelector('iframe')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses inline iframe confirmation on Android instead of window.confirm', async () => {
|
||||||
|
const publication = createPublication();
|
||||||
|
testState.capacitorPlatform = 'android';
|
||||||
|
testState.publicationType = 'reply';
|
||||||
|
testState.challenges = [
|
||||||
|
createStoredChallenge(
|
||||||
|
{
|
||||||
|
challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123',
|
||||||
|
type: 'url/iframe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...publication,
|
||||||
|
content: '>>17\nreply from android',
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
expect(confirmSpy).not.toHaveBeenCalled();
|
||||||
|
expect(container.textContent).toContain('spamblocker.bitsocial.net');
|
||||||
|
expect(container.textContent).toContain('mu wants to open spamblocker.bitsocial.net.\n\nFor reply: >>17 reply from android');
|
||||||
|
expect(container.textContent).toContain('Open');
|
||||||
|
expect(container.textContent).toContain('close');
|
||||||
|
expect(container.textContent).not.toContain('Challenge for reply');
|
||||||
|
expect(container.querySelector('iframe')).toBeNull();
|
||||||
|
|
||||||
|
await clickButton('Open');
|
||||||
|
|
||||||
|
const iframe = container.querySelector('iframe');
|
||||||
|
expect(iframe).not.toBeNull();
|
||||||
|
expect(iframe?.getAttribute('src')).toContain('https://spamblocker.bitsocial.net/api/v1/iframe/session-123?theme=dark');
|
||||||
|
expect(testState.abandonCurrentChallengeMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('abandons Android iframe challenges when inline confirmation is closed', async () => {
|
||||||
|
testState.capacitorPlatform = 'android';
|
||||||
|
testState.challenges = [
|
||||||
|
createStoredChallenge({
|
||||||
|
challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123',
|
||||||
|
type: 'url/iframe',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
await clickButton('close');
|
||||||
|
|
||||||
|
expect(confirmSpy).not.toHaveBeenCalled();
|
||||||
|
expect(testState.abandonCurrentChallengeMock).toHaveBeenCalledOnce();
|
||||||
|
expect(container.querySelector('iframe')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses inline iframe confirmation on Safari instead of window.confirm', async () => {
|
||||||
|
testState.publicationType = 'reply';
|
||||||
|
setNavigatorValue('userAgent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15');
|
||||||
|
setNavigatorValue('vendor', 'Apple Computer, Inc.');
|
||||||
|
testState.challenges = [
|
||||||
|
createStoredChallenge(
|
||||||
|
{
|
||||||
|
challenge: 'https://spamblocker.bitsocial.net/api/v1/iframe/session-123',
|
||||||
|
type: 'url/iframe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createPublication(),
|
||||||
|
content: '>>17\nreply from safari',
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
|
||||||
|
expect(confirmSpy).not.toHaveBeenCalled();
|
||||||
|
expect(container.textContent).toContain('mu wants to open spamblocker.bitsocial.net.\n\nFor reply: >>17 reply from safari');
|
||||||
|
expect(container.querySelector('iframe')).toBeNull();
|
||||||
|
|
||||||
|
await clickButton('Open');
|
||||||
|
|
||||||
|
const iframe = container.querySelector('iframe');
|
||||||
|
expect(iframe).not.toBeNull();
|
||||||
|
expect(iframe?.getAttribute('src')).toContain('https://spamblocker.bitsocial.net/api/v1/iframe/session-123?theme=dark');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows iframe challenge errors inline on Safari instead of relying on alert', async () => {
|
||||||
|
testState.account = { author: { address: '' } };
|
||||||
|
setNavigatorValue('userAgent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15');
|
||||||
|
setNavigatorValue('vendor', 'Apple Computer, Inc.');
|
||||||
|
testState.challenges = [
|
||||||
|
createStoredChallenge({
|
||||||
|
challenge: 'https://mintpass.org/auth?user={userAddress}',
|
||||||
|
type: 'url/iframe',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
await renderModal();
|
||||||
|
await clickButton('Open');
|
||||||
|
|
||||||
|
expect(alertSpy).not.toHaveBeenCalled();
|
||||||
|
expect(container.textContent).toContain('Error: Unable to load challenge without your address. Please sign in and try again.');
|
||||||
|
expect(container.textContent).not.toContain('Open');
|
||||||
|
expect(container.querySelector('iframe')).toBeNull();
|
||||||
|
|
||||||
|
await clickButton('close');
|
||||||
|
expect(testState.abandonCurrentChallengeMock).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
it('alerts when iframe challenges need a signer address and the account is missing one', async () => {
|
it('alerts when iframe challenges need a signer address and the account is missing one', async () => {
|
||||||
testState.account = { author: { address: '' } };
|
testState.account = { author: { address: '' } };
|
||||||
testState.challenges = [
|
testState.challenges = [
|
||||||
|
|||||||
@@ -112,6 +112,18 @@
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iframeConsentWrapper {
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin: 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframeConsentMessage {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: normal;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
.iframe {
|
.iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useRef, useState, useEffect, useCallback } from 'react';
|
import { useRef, useState, useEffect, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Challenge as ChallengeType, useAccount, useComment } from '@bitsocial/bitsocial-react-hooks';
|
import { Challenge as ChallengeType, useAccount, useComment } from '@bitsocial/bitsocial-react-hooks';
|
||||||
|
import { Capacitor } from '@capacitor/core';
|
||||||
import { getPublicationPreview, getPublicationType, getVotePreview, type ChallengePublication } from '../../lib/utils/challenge-utils';
|
import { getPublicationPreview, getPublicationType, getVotePreview, type ChallengePublication } from '../../lib/utils/challenge-utils';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
import useChallengesStore from '../../stores/use-challenges-store';
|
import useChallengesStore from '../../stores/use-challenges-store';
|
||||||
@@ -44,6 +45,23 @@ const getDisplayCommunityAddress = (shortCommunityAddress?: string, communityAdd
|
|||||||
shortCommunityAddress || (communityAddress ? getShortAddress(communityAddress) : '') || communityAddress || '';
|
shortCommunityAddress || (communityAddress ? getShortAddress(communityAddress) : '') || communityAddress || '';
|
||||||
|
|
||||||
const iframeChallengeConfirmDecisions = new Map<string, 'accepted' | 'rejected'>();
|
const iframeChallengeConfirmDecisions = new Map<string, 'accepted' | 'rejected'>();
|
||||||
|
const isIosWebKitUserAgent = () => {
|
||||||
|
if (typeof navigator === 'undefined') return false;
|
||||||
|
return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDesktopSafariUserAgent = () => {
|
||||||
|
if (typeof navigator === 'undefined') return false;
|
||||||
|
const isAppleVendor = navigator.vendor === 'Apple Computer, Inc.';
|
||||||
|
const isSafari = /Safari/.test(navigator.userAgent);
|
||||||
|
const isOtherBrowser = /Chrome|Chromium|CriOS|FxiOS|Edg|OPR|SamsungBrowser/.test(navigator.userAgent);
|
||||||
|
return isAppleVendor && isSafari && !isOtherBrowser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldUseInlineIframeConfirm = () => {
|
||||||
|
const platform = Capacitor.getPlatform();
|
||||||
|
return platform === 'android' || platform === 'ios' || isIosWebKitUserAgent() || isDesktopSafariUserAgent();
|
||||||
|
};
|
||||||
|
|
||||||
const TextChallenge = ({ challenge }: { challenge: string }) => <div className={styles.challengeMedia}>{challenge}</div>;
|
const TextChallenge = ({ challenge }: { challenge: string }) => <div className={styles.challengeMedia}>{challenge}</div>;
|
||||||
|
|
||||||
@@ -112,17 +130,34 @@ interface IframeChallengeProps {
|
|||||||
challenge: string;
|
challenge: string;
|
||||||
confirmKey: string;
|
confirmKey: string;
|
||||||
confirmMessage: string;
|
confirmMessage: string;
|
||||||
|
inlineConfirm: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onDone: () => void;
|
onDone: () => void;
|
||||||
onAutoComplete: (challengeAnswers: string[]) => void;
|
onAutoComplete: (challengeAnswers: string[]) => void;
|
||||||
onReady: () => void;
|
onReady: () => void;
|
||||||
|
openLabel: string;
|
||||||
|
closeLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IframeChallenge = ({ challenge, confirmKey, confirmMessage, onCancel, onDone, onAutoComplete, onReady }: IframeChallengeProps) => {
|
type IframeConfirmSource = 'native' | 'inline';
|
||||||
|
|
||||||
|
const IframeChallenge = ({
|
||||||
|
challenge,
|
||||||
|
confirmKey,
|
||||||
|
confirmMessage,
|
||||||
|
inlineConfirm,
|
||||||
|
onCancel,
|
||||||
|
onDone,
|
||||||
|
onAutoComplete,
|
||||||
|
onReady,
|
||||||
|
openLabel,
|
||||||
|
closeLabel,
|
||||||
|
}: IframeChallengeProps) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const [theme] = useTheme();
|
const [theme] = useTheme();
|
||||||
const [iframeUrlState, setIframeUrl] = useState('');
|
const [iframeUrlState, setIframeUrl] = useState('');
|
||||||
const [iframeOrigin, setIframeOrigin] = useState('');
|
const [iframeOrigin, setIframeOrigin] = useState('');
|
||||||
|
const [inlineErrorMessage, setInlineErrorMessage] = useState('');
|
||||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
const attemptedLoadRef = useRef(false);
|
const attemptedLoadRef = useRef(false);
|
||||||
const mountedRef = useRef(false);
|
const mountedRef = useRef(false);
|
||||||
@@ -148,59 +183,78 @@ const IframeChallenge = ({ challenge, confirmKey, confirmMessage, onCancel, onDo
|
|||||||
[onReady],
|
[onReady],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoadIframe = useCallback(() => {
|
const handleLoadIframe = useCallback(
|
||||||
const iframeUrl = challenge;
|
(confirmSource: IframeConfirmSource) => {
|
||||||
if (!iframeUrl) return;
|
const iframeUrl = challenge;
|
||||||
|
if (!iframeUrl) return;
|
||||||
|
|
||||||
const rawUserAddress = account?.author?.address?.trim();
|
const rejectChallenge = (message: string) => {
|
||||||
const requiresUserAddress = iframeUrl.includes('{userAddress}');
|
|
||||||
|
|
||||||
if (requiresUserAddress && !rawUserAddress) {
|
|
||||||
alert('Error: Unable to load challenge without your address. Please sign in and try again.');
|
|
||||||
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
|
||||||
onCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const encodedAddress = rawUserAddress ? encodeURIComponent(rawUserAddress) : undefined;
|
|
||||||
const replacedUrl = requiresUserAddress && encodedAddress ? iframeUrl.replace(/\{userAddress\}/g, encodedAddress) : iframeUrl;
|
|
||||||
|
|
||||||
const validatedUrl = validateIframeChallengeUrl(replacedUrl, theme);
|
|
||||||
if (validatedUrl.status === 'unsupported') {
|
|
||||||
alert('Error: Only HTTPS iframe challenges or localhost HTTP challenges are supported');
|
|
||||||
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
|
||||||
onCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (validatedUrl.status === 'invalid') {
|
|
||||||
console.error('Invalid iframe challenge URL', { error: validatedUrl.error });
|
|
||||||
alert('Error: Invalid URL for authentication challenge');
|
|
||||||
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
|
||||||
onCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const decision = iframeChallengeConfirmDecisions.get(confirmKey);
|
|
||||||
if (decision === 'rejected') {
|
|
||||||
onCancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (decision !== 'accepted') {
|
|
||||||
if (!window.confirm(confirmMessage)) {
|
|
||||||
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
||||||
|
if (inlineConfirm) {
|
||||||
|
setInlineErrorMessage(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rawUserAddress = account?.author?.address?.trim();
|
||||||
|
const requiresUserAddress = iframeUrl.includes('{userAddress}');
|
||||||
|
|
||||||
|
if (requiresUserAddress && !rawUserAddress) {
|
||||||
|
rejectChallenge('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;
|
||||||
|
|
||||||
|
const validatedUrl = validateIframeChallengeUrl(replacedUrl, theme);
|
||||||
|
if (validatedUrl.status === 'unsupported') {
|
||||||
|
rejectChallenge('Error: Only HTTPS iframe challenges or localhost HTTP challenges are supported');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (validatedUrl.status === 'invalid') {
|
||||||
|
console.error('Invalid iframe challenge URL', { error: validatedUrl.error });
|
||||||
|
rejectChallenge('Error: Invalid URL for authentication challenge');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const decision = iframeChallengeConfirmDecisions.get(confirmKey);
|
||||||
|
if (decision === 'rejected') {
|
||||||
onCancel();
|
onCancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
iframeChallengeConfirmDecisions.set(confirmKey, 'accepted');
|
if (decision !== 'accepted') {
|
||||||
}
|
if (confirmSource === 'native' && !window.confirm(confirmMessage)) {
|
||||||
openValidatedIframe(validatedUrl);
|
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
||||||
}, [account, challenge, confirmKey, confirmMessage, onCancel, openValidatedIframe, theme]);
|
onCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
iframeChallengeConfirmDecisions.set(confirmKey, 'accepted');
|
||||||
|
}
|
||||||
|
openValidatedIframe(validatedUrl);
|
||||||
|
},
|
||||||
|
[account, challenge, confirmKey, confirmMessage, inlineConfirm, onCancel, openValidatedIframe, theme],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (inlineConfirm) return;
|
||||||
if (attemptedLoadRef.current) return;
|
if (attemptedLoadRef.current) return;
|
||||||
attemptedLoadRef.current = true;
|
attemptedLoadRef.current = true;
|
||||||
handleLoadIframe();
|
handleLoadIframe('native');
|
||||||
|
}, [handleLoadIframe, inlineConfirm]);
|
||||||
|
|
||||||
|
const handleInlineConfirm = useCallback(() => {
|
||||||
|
if (attemptedLoadRef.current) return;
|
||||||
|
attemptedLoadRef.current = true;
|
||||||
|
handleLoadIframe('inline');
|
||||||
}, [handleLoadIframe]);
|
}, [handleLoadIframe]);
|
||||||
|
|
||||||
|
const handleInlineCancel = useCallback(() => {
|
||||||
|
iframeChallengeConfirmDecisions.set(confirmKey, 'rejected');
|
||||||
|
onCancel();
|
||||||
|
}, [confirmKey, onCancel]);
|
||||||
|
|
||||||
const sendThemeToIframe = useCallback(() => {
|
const sendThemeToIframe = useCallback(() => {
|
||||||
postThemeToIframe(iframeRef.current, iframeOrigin, theme);
|
postThemeToIframe(iframeRef.current, iframeOrigin, theme);
|
||||||
}, [iframeOrigin, theme]);
|
}, [iframeOrigin, theme]);
|
||||||
@@ -240,6 +294,27 @@ const IframeChallenge = ({ challenge, confirmKey, confirmMessage, onCancel, onDo
|
|||||||
}, [expectedSessionId, iframeOrigin, onAutoComplete]);
|
}, [expectedSessionId, iframeOrigin, onAutoComplete]);
|
||||||
|
|
||||||
if (!iframeUrlState) {
|
if (!iframeUrlState) {
|
||||||
|
if (inlineConfirm) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={`${styles.challengeMediaWrapper} ${styles.iframeConsentWrapper}`}>
|
||||||
|
<div className={styles.iframeConsentMessage}>{inlineErrorMessage || confirmMessage}</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.challengeFooter} ${styles.iframeFooter}`}>
|
||||||
|
<span>
|
||||||
|
{!inlineErrorMessage && (
|
||||||
|
<button type='button' onClick={handleInlineConfirm}>
|
||||||
|
{openLabel}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button type='button' onClick={handleInlineCancel}>
|
||||||
|
{closeLabel}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +373,8 @@ const Challenge = ({ challenge, challengeId, closeModal, abandonModal }: Challen
|
|||||||
const isTextChallenge = currentChallenge?.type === 'text/plain';
|
const isTextChallenge = currentChallenge?.type === 'text/plain';
|
||||||
const isImageChallenge = currentChallenge?.type === 'image/png';
|
const isImageChallenge = currentChallenge?.type === 'image/png';
|
||||||
const isIframeChallenge = currentChallenge?.type === 'url/iframe';
|
const isIframeChallenge = currentChallenge?.type === 'url/iframe';
|
||||||
const isIframePending = isIframeChallenge && readyIframeChallengeKey !== iframeChallengeKey;
|
const inlineIframeConfirm = isIframeChallenge && shouldUseInlineIframeConfirm();
|
||||||
|
const isIframePending = isIframeChallenge && !inlineIframeConfirm && readyIframeChallengeKey !== iframeChallengeKey;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
@@ -463,10 +539,13 @@ const Challenge = ({ challenge, challengeId, closeModal, abandonModal }: Challen
|
|||||||
challenge={currentChallenge?.challenge ?? ''}
|
challenge={currentChallenge?.challenge ?? ''}
|
||||||
confirmKey={iframeChallengeKey}
|
confirmKey={iframeChallengeKey}
|
||||||
confirmMessage={iframeConfirmMessage}
|
confirmMessage={iframeConfirmMessage}
|
||||||
|
inlineConfirm={inlineIframeConfirm}
|
||||||
onCancel={abandonModal}
|
onCancel={abandonModal}
|
||||||
onDone={onIframeDone}
|
onDone={onIframeDone}
|
||||||
onAutoComplete={onIframeAutoComplete}
|
onAutoComplete={onIframeAutoComplete}
|
||||||
onReady={onIframeReady}
|
onReady={onIframeReady}
|
||||||
|
openLabel={capitalize(t('open'))}
|
||||||
|
closeLabel={t('close')}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user