-
Notifications
You must be signed in to change notification settings - Fork 3
[fix] 웹뷰 네비게이션 뒤로가기 버그 수정 및 useNavigator 개선 #1507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f02ddd4
14afa5c
b2307d6
74d9542
dc76248
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,12 @@ | ||
| import { useCallback } from 'react'; | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import isInAppWebView from '@/utils/isInAppWebView'; | ||
| import { requestOpenExternalUrl } from '@/utils/webviewBridge'; | ||
| import { | ||
| requestNavigateWebview, | ||
| requestOpenExternalUrl, | ||
| } from '@/utils/webviewBridge'; | ||
|
|
||
| const toSlug = (path: string) => path.replace(/^\//, ''); | ||
|
|
||
| const useNavigator = () => { | ||
| const navigate = useNavigate(); | ||
|
|
@@ -10,28 +15,20 @@ const useNavigator = () => { | |
| (url: string) => { | ||
| const trimmedUrl = url?.trim(); | ||
| if (!trimmedUrl) return; | ||
| if (/^(javascript|data|vbscript):/i.test(trimmedUrl)) return; | ||
|
|
||
| const inWebview = isInAppWebView(); | ||
| const isExternal = /^(https?|itms-apps):\/\//.test(trimmedUrl); | ||
|
|
||
| const isDangerousProtocol = /^(javascript|data|vbscript):/i.test( | ||
| trimmedUrl, | ||
| ); | ||
| if (isDangerousProtocol) return; | ||
|
|
||
| const isExternalUrl = /^(https?|itms-apps):\/\//.test(trimmedUrl); | ||
|
|
||
| if (isExternalUrl) { | ||
| // 웹뷰에서 window.location.href로 외부 URL을 열면 WebView 자체가 이동해버리므로 앱에 위임. | ||
| // requestOpenExternalUrl은 http/https만 허용하므로, itms-apps:// 등 비표준 스킴은 false를 | ||
| // 반환 → window.open으로 폴백해 OS가 처리하도록 위임 | ||
| if (isInAppWebView()) { | ||
| if (!requestOpenExternalUrl(trimmedUrl)) { | ||
| window.open(trimmedUrl); | ||
| } | ||
| } else { | ||
| window.location.href = trimmedUrl; | ||
| } | ||
| } else { | ||
| navigate(trimmedUrl); | ||
| if (isExternal) { | ||
| if (inWebview && !requestOpenExternalUrl(trimmedUrl)) | ||
| window.open(trimmedUrl); | ||
| else if (!inWebview) window.location.href = trimmedUrl; | ||
| return; | ||
| } | ||
|
|
||
| if (inWebview) requestNavigateWebview(toSlug(trimmedUrl)); | ||
| else navigate(trimmedUrl); | ||
|
Comment on lines
+30
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 웹뷰 브리지 실패 시 내부 링크가 무반응이 됩니다.
수정 예시- if (inWebview) requestNavigateWebview(toSlug(trimmedUrl));
- else navigate(trimmedUrl);
+ if (inWebview) {
+ const delegated = requestNavigateWebview(toSlug(trimmedUrl));
+ if (!delegated) navigate(trimmedUrl);
+ return;
+ }
+
+ navigate(trimmedUrl);🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. requestNavigateWebview는 이미 inWebview === true인 브랜치 안에서 호출됩니다. 이 함수가 false를 반환하는 유일한 경우는 isInAppWebView()가 false일 때인데, 해당 브랜치에 진입했다는 것 자체가 그 조건을 이미 통과했음을 의미합니다. 의미 없는 폴백을 추가하는 것은 오히려 잘못된 동작을 정상처럼 보이게 만듭니다.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 36 🏁 Script executed: Length of output: 4110
첫 번째 주장에 대해 한 가지 짚고 싶은 점이 있습니다. 그러나 두 번째 주장이 결정적입니다. 지적하신 내용이 맞습니다. 리뷰 코멘트를 철회하겠습니다. ✏️ Learnings added
|
||
| }, | ||
| [navigate], | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||||||||||||||
| import { useNavigate } from 'react-router-dom'; | ||||||||||||||||||||||||||
| import { USER_EVENT } from '@/constants/eventName'; | ||||||||||||||||||||||||||
| import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; | ||||||||||||||||||||||||||
| import useNavigator from '@/hooks/useNavigator'; | ||||||||||||||||||||||||||
| import isInAppWebView from '@/utils/isInAppWebView'; | ||||||||||||||||||||||||||
| import { requestNavigateWebview } from '@/utils/webviewBridge'; | ||||||||||||||||||||||||||
| import ArrowButton from '../PromotionArrowButton/PromotionArrowButton'; | ||||||||||||||||||||||||||
|
|
@@ -12,7 +12,7 @@ interface Props { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const PromotionClubCTA = ({ clubId, clubName }: Props) => { | ||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||
| const handleLink = useNavigator(); | ||||||||||||||||||||||||||
| const trackEvent = useMixpanelTrack(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const handleNavigate = () => { | ||||||||||||||||||||||||||
|
|
@@ -21,10 +21,11 @@ const PromotionClubCTA = ({ clubId, clubName }: Props) => { | |||||||||||||||||||||||||
| club_name: clubName, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 웹뷰는 club/id 기반 slug, 일반 웹은 clubName 기반 경로 사용 | ||||||||||||||||||||||||||
| if (isInAppWebView()) { | ||||||||||||||||||||||||||
| requestNavigateWebview(`club/${clubId}`); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| navigate(`/clubDetail/@${encodeURIComponent(clubName)}`); | ||||||||||||||||||||||||||
| handleLink(`/clubDetail/@${encodeURIComponent(clubName)}`); | ||||||||||||||||||||||||||
|
Comment on lines
+24
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CTA도 웹뷰 브리지 실패 시 그대로 죽습니다. 여기서는 수정 예시- if (isInAppWebView()) {
- requestNavigateWebview(`club/${clubId}`);
- } else {
- handleLink(`/clubDetail/@${encodeURIComponent(clubName)}`);
- }
+ if (isInAppWebView() && requestNavigateWebview(`club/${clubId}`)) {
+ return;
+ }
+
+ handleLink(`/clubDetail/@${encodeURIComponent(clubName)}`);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실패 시 handleLink('/clubDetail/@clubname')으로 폴백하면 useNavigator 내부에서 다시 inWebview === true 브랜치로 진입해 requestNavigateWebview를 재호출하는 무한루프가 발생합니다.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
현재 구조( (∩`-´)⊃━☆゚.*・。゚ ✏️ Learnings added
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
외부 URL 스킴 판별은 대소문자를 무시해야 합니다.
지금 정규식은
HTTPS://...나ITMS-APPS://...를 내부 경로로 분류합니다. 그러면 일반 웹에서는 React Router로, 웹뷰에서는NAVIGATE_WEBVIEW로 잘못 보내지게 됩니다.수정 예시
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
타당하지 않습니다. 적용 불필요합니다.
URL 스킴은 RFC 3986 기준으로 대소문자를 구분하지 않지만, 실제로 HTTPS:// 같은 대문자 URL을 입력하는 경우는 없습니다. 브라우저, 앱, 백엔드 모두 소문자 스킴으로 URL을 생성하고, 사용자가 직접 입력하는 경우도 거의 없습니다.
이미 위의 위험 프로토콜 차단 정규식(line 18)에는 i 플래그가 있는 것도 실제 XSS 공격 벡터를 막기 위한 것이고, 외부 URL 판별은 그런 위협이 없습니다. 과잉 방어입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oesnuj, 말씀하신 내용 이해했습니다. 실제 환경에서 대문자 스킴이 사용되는 경우가 없고, 위험 프로토콜 차단과는 위협 모델이 다르다는 점에서 타당한 판단입니다. 제안을 철회하겠습니다.