@@ -12,8 +12,9 @@ import SkeletonGuard from '@/shared/components/SkeletonGuard';
1212import { useParam } from '@/shared/hooks/useParam' ;
1313
1414// === Deep Link: 강화 폴백 유틸 시작 ===
15- const IOS_STORE_URL =
15+ const IOS_STORE_URL_WEB =
1616 'https://apps.apple.com/kr/app/%ED%88%AD-%EB%A7%8C%EB%82%8C%EC%9D%84-%EB%84%8C%EC%A7%80%EC%8B%9C/id6749781762' ;
17+ const IOS_STORE_URL_APPS = 'itms-apps://itunes.apple.com/app/id6749781762' ; // App Store 앱 직접 오픈
1718const ANDROID_STORE_URL = 'https://play.google.com/store/apps/details?id=com.plottwist.tuk' ;
1819
1920function buildAndroidIntent ( path : string , playUrl : string ) {
@@ -31,63 +32,68 @@ function buildAndroidIntent(path: string, playUrl: string) {
3132function openProposalInApp ( proposalId : number | string ) {
3233 const ua = navigator . userAgent . toLowerCase ( ) ;
3334 const isAndroid = ua . includes ( 'android' ) ;
34-
35- // 인앱 브라우저(대략) 감지 — 카톡/인스타 등
36- const isInApp = / \b ( k a k a o t a l k | i n s t a g r a m | l i n e | f b _ i a b | f b a v | t w i t t e r | n a v e r ( i n a p p ) ? | d a u m a p p s ) \b / . test (
37- ua
38- ) ;
35+ const isIOS = / i p h o n e | i p a d | i p o d / . test ( ua ) ;
3936
4037 // 공통 스킴 (앱에서 열릴 실제 진입 위치)
41- const iosScheme = `tuk-app://tuk/proposal-detail/${ encodeURIComponent ( String ( proposalId ) ) } ` ;
42- const androidPath = `tuk/proposal-detail/${ encodeURIComponent ( String ( proposalId ) ) } ` ;
38+ const schemeUrl = `tuk-app://tuk/proposal-detail/${ encodeURIComponent ( String ( proposalId ) ) } ` ;
4339
44- // Android: 가능하면 intent:// 로 한 방에 폴백 자동 처리
4540 if ( isAndroid ) {
41+ // 안드로이드는 기존 intent 유지 (정상 동작 중)
42+ const androidPath = `tuk/proposal-detail/${ encodeURIComponent ( String ( proposalId ) ) } ` ;
4643 const intentUrl = buildAndroidIntent ( androidPath , ANDROID_STORE_URL ) ;
47- // 일부 인앱 브라우저는 intent 처리에 제약이 있어도, 시도 → 스토어 폴백은 작동
4844 window . location . href = intentUrl ;
4945 return ;
5046 }
5147
52- // iOS (및 기타): 스킴 시도 → 전환 신호 없으면 스토어로 replace
53- const fallbackDelayMs = 1700 ; // 1500~1800 권장
54- let timer : number | null = null ;
55-
56- const cleanup = ( ) => {
57- if ( timer ) {
58- window . clearTimeout ( timer ) ;
59- timer = null ;
60- }
61- document . removeEventListener ( 'visibilitychange' , onHide , true ) ;
62- window . removeEventListener ( 'pagehide' , onHide , true ) ;
63- window . removeEventListener ( 'blur' , onHide , true ) ;
64- } ;
65- const onHide = ( ) => {
66- // 앱으로 전환되면 페이지가 숨김/이탈/블러됨 → 타이머 취소
67- cleanup ( ) ;
68- } ;
69-
70- document . addEventListener ( 'visibilitychange' , onHide , true ) ;
71- window . addEventListener ( 'pagehide' , onHide , true ) ;
72- window . addEventListener ( 'blur' , onHide , true ) ;
73-
74- // 인앱 브라우저일 때 커스텀 스킴이 막히는 케이스가 흔하지만
75- // 그래도 한번 시도 후 폴백(UX 유지)
76- try {
77- window . location . href = iosScheme ;
78- } catch {
79- /* empty */
48+ if ( isIOS ) {
49+ // iOS: 히든 iframe으로 조용히 스킴 시도 → 실패 시 App Store 앱(itms-apps://) 폴백
50+ const fallbackDelayMs = 1500 ; // 1300~1700 권장
51+ let timer : number | null = null ;
52+
53+ const cleanup = ( ) => {
54+ if ( timer ) {
55+ window . clearTimeout ( timer ) ;
56+ timer = null ;
57+ }
58+ document . removeEventListener ( 'visibilitychange' , onHide , true ) ;
59+ window . removeEventListener ( 'pagehide' , onHide , true ) ;
60+ window . removeEventListener ( 'blur' , onHide , true ) ;
61+ const iframe = document . getElementById ( '__dl_iframe__' ) ;
62+ if ( iframe && iframe . parentNode ) iframe . parentNode . removeChild ( iframe ) ;
63+ } ;
64+ const onHide = ( ) => {
65+ // 앱으로 전환되면 페이지가 숨김/이탈/블러됨 → 타이머 취소
66+ cleanup ( ) ;
67+ } ;
68+
69+ document . addEventListener ( 'visibilitychange' , onHide , true ) ;
70+ window . addEventListener ( 'pagehide' , onHide , true ) ;
71+ window . addEventListener ( 'blur' , onHide , true ) ;
72+
73+ // 히든 iframe으로 스킴 시도 (미설치 시 알럿 없이 조용히 실패)
74+ const iframe = document . createElement ( 'iframe' ) ;
75+ iframe . style . display = 'none' ;
76+ iframe . id = '__dl_iframe__' ;
77+ iframe . src = schemeUrl ;
78+ document . body . appendChild ( iframe ) ;
79+
80+ // 전환이 없으면 App Store 앱을 직접 오픈 (itms-apps://)
81+ timer = window . setTimeout ( ( ) => {
82+ try {
83+ window . location . replace ( IOS_STORE_URL_APPS ) ;
84+ } catch {
85+ // 혹시 itms-apps가 막히면 웹 스토어로 폴백
86+ window . location . replace ( IOS_STORE_URL_WEB ) ;
87+ } finally {
88+ cleanup ( ) ;
89+ }
90+ } , fallbackDelayMs ) ;
91+
92+ return ;
8093 }
8194
82- timer = window . setTimeout (
83- ( ) => {
84- // 전환 신호 없으면 미설치/차단으로 판단 → 스토어로 이동
85- // replace를 써서 '뒤로 가기' 시 빈 페이지/루프 방지
86- window . location . replace ( IOS_STORE_URL ) ;
87- cleanup ( ) ;
88- } ,
89- isInApp ? 1200 : fallbackDelayMs
90- ) ; // 인앱 브라우저는 약간 더 짧게
95+ // 기타 환경(데스크톱 등)은 앱 스토어 웹 페이지로
96+ window . location . href = IOS_STORE_URL_WEB ;
9197}
9298// === Deep Link: 강화 폴백 유틸 끝 ===
9399
0 commit comments