diff --git a/app/webview/[slug].tsx b/app/webview/[slug].tsx index a5c4e70..d7ad962 100644 --- a/app/webview/[slug].tsx +++ b/app/webview/[slug].tsx @@ -76,7 +76,14 @@ export default function WebViewScreen() { ? (config.url ?? (config.path ? `${webviewUrl}${config.path}` : "")) : ""; - const url = useMemo(() => appendSessionId(baseUrl, sessionId), [baseUrl, sessionId]); + /** + * session_id 는 웹 Mixpanel 의 distinct_id 라 모아동 밖으로 나가면 안 된다. + * 이 화면은 slug=external 로 임의 외부 URL 도 로드하므로 오리진을 확인하고 붙인다. + */ + const url = useMemo( + () => (isWebViewOrigin(baseUrl) ? appendSessionId(baseUrl, sessionId) : baseUrl), + [baseUrl, sessionId], + ); /** * 우체통은 앱이 주입한 학생 토큰을 먼저 쓴다(웹 studentFetch). 주입이 없으면 웹이 diff --git a/ui/home/home-webview-screen.tsx b/ui/home/home-webview-screen.tsx index 5c79613..e76751c 100644 --- a/ui/home/home-webview-screen.tsx +++ b/ui/home/home-webview-screen.tsx @@ -2,7 +2,7 @@ import { useHomeWebViewPreloadContext } from '@/contexts/home-webview-preload-co import { useMixpanelContext } from '@/contexts/mixpanel-context'; import { useSubscribedClubsContext } from '@/contexts/subscribed-clubs-context'; import { ensureAccessToken } from '@/services/auth-token.service'; -import { appendSessionId, buildStudentTokenInjection, getWebViewUserAgent } from '@/utils/webview'; +import { appendSessionId, buildStudentTokenInjection, getWebViewUserAgent, isWebViewOrigin } from '@/utils/webview'; import Constants from 'expo-constants'; import { useRouter } from 'expo-router'; import * as WebBrowser from 'expo-web-browser'; @@ -174,8 +174,10 @@ export function HomeWebViewScreen({ onError }: HomeWebViewScreenProps) { const handleShouldStartLoadWithRequest = useCallback( (request: ShouldStartLoadRequest) => { - const baseOrigin = (process.env.EXPO_PUBLIC_WEBVIEW_URL ?? 'https://moadong.com').replace(/\/$/, ''); - if (request.url.startsWith('http') && !request.url.startsWith(baseOrigin)) { + // origin 을 문자열 prefix 로 판정하면 moadong.com.evil.com 이 내부로 통과한다. + // 그 페이지가 이 웹뷰에 뜨면 window.ReactNativeWebView.postMessage 로 브리지를 + // 그대로 쓸 수 있다(SUBSCRIBE_TOGGLE, OPEN_EXTERNAL_URL 등). 파싱해서 비교한다. + if (request.url.startsWith('http') && !isWebViewOrigin(request.url)) { // iOS: navigationType === 'click' 은 사용자가 직접 링크를 탭한 경우만 해당 // 초기 로드·서버 리다이렉트는 'other' 이므로 인터셉트하지 않음 // Android: navigationType이 항상 'other'이므로 loaded 상태로 구분 @@ -230,6 +232,10 @@ export function HomeWebViewScreen({ onError }: HomeWebViewScreenProps) { onHttpError={handleError} javaScriptEnabled domStorageEnabled + // Android 기본값(true)이면 target=_blank 가 onCreateWindow 로 가는데, + // onOpenWindow 핸들러가 없으면 화면에 붙지 않는 WebView 로 빨려들어가 링크가 죽는다. + // false 로 두면 같은 요청이 onShouldStartLoadWithRequest 를 타 iOS 와 같은 경로가 된다. + setSupportMultipleWindows={false} pullToRefreshEnabled allowsBackForwardNavigationGestures />