Skip to content

Commit e81c5f7

Browse files
authored
Merge pull request #74 from Nexters/fix/deep-link-logic
fix: 모바일 웹 딥링크 로직 전체 수정
2 parents 74d6ffc + f6c248f commit e81c5f7

File tree

4 files changed

+131
-11
lines changed

4 files changed

+131
-11
lines changed

apps/tuk-web/src/app/invite/gathering/[gatheringId]/src/components/InviteGathering.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import InviteGatheringContent from '@/app/invite/gathering/[gatheringId]/src/com
88
import InviteGatheringErrorFallback from '@/app/invite/gathering/[gatheringId]/src/components/InviteGatheringErrorFallback';
99
import InviteGatheringSkeleton from '@/app/invite/gathering/[gatheringId]/src/components/InviteGatheringSkeleton';
1010
import { BackgroundTemplate, Button } from '@/shared/components';
11+
import { useUserAgent } from '@/shared/components/provider/UserAgentProvider';
1112
import SkeletonGuard from '@/shared/components/SkeletonGuard';
1213
import { useParam } from '@/shared/hooks/useParam';
14+
import { joinGatheringInAPP } from '@/shared/lib/deeplink';
1315

1416
const InviteGathering = () => {
1517
const gatheringId = Number(useParam('gatheringId'));
1618

19+
const userAgent = useUserAgent();
20+
1721
return (
1822
<BackgroundTemplate>
1923
<BackgroundTemplate.Main className="px-5">
@@ -32,12 +36,7 @@ const InviteGathering = () => {
3236
</QueryErrorResetBoundary>
3337

3438
<BackgroundTemplate.CTA>
35-
<Button
36-
className="w-full"
37-
onClick={() =>
38-
(window.location.href = `tuk-app://tuk/join-gathering?gatheringId=${gatheringId}`)
39-
}
40-
>
39+
<Button className="w-full" onClick={() => joinGatheringInAPP(gatheringId, userAgent)}>
4140
입장하기
4241
</Button>
4342
</BackgroundTemplate.CTA>

apps/tuk-web/src/app/invite/meet/[meetId]/src/components/InviteProposal.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import InviteProposalContent from '@/app/invite/meet/[meetId]/src/components/Inv
88
import InviteProposalErrorFallback from '@/app/invite/meet/[meetId]/src/components/InviteProposalErrorFallback';
99
import InviteProposalSkeleton from '@/app/invite/meet/[meetId]/src/components/InviteProposalSkeleton';
1010
import { BackgroundTemplate, Button } from '@/shared/components';
11+
import { useUserAgent } from '@/shared/components/provider/UserAgentProvider';
1112
import SkeletonGuard from '@/shared/components/SkeletonGuard';
1213
import { useParam } from '@/shared/hooks/useParam';
14+
import { joinProposalInAPP } from '@/shared/lib/deeplink';
1315

1416
const InviteProposal = () => {
1517
const proposalId = Number(useParam('meetId'));
1618

19+
const userAgent = useUserAgent();
20+
1721
return (
1822
<BackgroundTemplate>
1923
<BackgroundTemplate.Main className="px-5">
@@ -32,10 +36,7 @@ const InviteProposal = () => {
3236
</QueryErrorResetBoundary>
3337

3438
<BackgroundTemplate.CTA>
35-
<Button
36-
className="w-full"
37-
onClick={() => (window.location.href = `tuk-app://tuk/proposal-detail/${proposalId}`)}
38-
>
39+
<Button className="w-full" onClick={() => joinProposalInAPP(proposalId, userAgent)}>
3940
초대장 확인하기
4041
</Button>
4142
</BackgroundTemplate.CTA>

apps/tuk-web/src/shared/components/AppInstallBanner.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
'use client';
22

33
import { CloseIcon24 } from '@/shared/components/icon';
4+
import { useUserAgent } from '@/shared/components/provider/UserAgentProvider';
5+
import { joinHomeInAPP } from '@/shared/lib/deeplink';
46

57
const AppInstallBanner = ({ onClose }: { onClose: () => void }) => {
8+
const userAgent = useUserAgent();
9+
610
return (
711
<div className="fixed left-1/2 top-0 z-20 flex h-[calc(56px+env(safe-area-inset-top))] w-full max-w-[600px] -translate-x-1/2 items-center justify-between bg-gray-50 px-5 pt-[max(0px,env(safe-area-inset-top))] text-black-default shadow-[0_1px_0_0_rgba(0,0,0,0.04)] backdrop-blur-[2px]">
812
<div className="flex items-center gap-2">
@@ -17,7 +21,7 @@ const AppInstallBanner = ({ onClose }: { onClose: () => void }) => {
1721

1822
<button
1923
className="pretendard-body-12-B rounded-[1.25rem] bg-gray-900 px-2.5 py-2 text-white-default"
20-
onClick={() => (window.location.href = 'tuk-app://tuk')}
24+
onClick={() => joinHomeInAPP(userAgent)}
2125
>
2226
앱 열기
2327
</button>
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { UserAgent } from '@/shared/components/provider/UserAgentProvider';
2+
3+
const FALL_BACK_DELAY_MS = 1500;
4+
5+
const ANDROID_STORE_URL = 'https://play.google.com/store/apps/details?id=com.plottwist.tuk';
6+
const IOS_STORE_URL_WEB =
7+
'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';
8+
const IOS_STORE_URL_APPS = 'itms-apps://itunes.apple.com/app/id6749781762';
9+
10+
const GATHERING_DEEP_LINK = (gatheringId: number) =>
11+
`tuk-app://tuk/join-gathering?gatheringId=${gatheringId}`;
12+
13+
const PROPOSAL_DEEP_LINK = (proposalId: number) => `tuk-app://tuk/proposal-detail/${proposalId}`;
14+
15+
export const joinProposalInAPP = (proposalId: number, userAgent: UserAgent) => {
16+
if (userAgent.isAndroid) {
17+
window.location.href = generateAndroidIntentURL(
18+
`tuk/proposal-detail/${proposalId}`,
19+
ANDROID_STORE_URL
20+
);
21+
22+
return;
23+
}
24+
25+
if (userAgent.isIOS) {
26+
appendIOSIframe(PROPOSAL_DEEP_LINK(proposalId));
27+
28+
return;
29+
}
30+
31+
window.location.href = IOS_STORE_URL_WEB;
32+
};
33+
34+
export const joinGatheringInAPP = (gatheringId: number, userAgent: UserAgent) => {
35+
if (userAgent.isAndroid) {
36+
window.location.href = generateAndroidIntentURL(
37+
`tuk/join-gathering?gatheringId=${gatheringId}`,
38+
ANDROID_STORE_URL
39+
);
40+
return;
41+
}
42+
43+
if (userAgent.isIOS) {
44+
appendIOSIframe(GATHERING_DEEP_LINK(gatheringId));
45+
46+
return;
47+
}
48+
49+
window.location.href = IOS_STORE_URL_WEB;
50+
};
51+
52+
export const joinHomeInAPP = (userAgent: UserAgent) => {
53+
if (userAgent.isAndroid) {
54+
window.location.href = generateAndroidIntentURL('tuk', ANDROID_STORE_URL);
55+
56+
return;
57+
}
58+
59+
if (userAgent.isIOS) {
60+
appendIOSIframe('tuk-app://tuk');
61+
62+
return;
63+
}
64+
};
65+
66+
const generateAndroidIntentURL = (deepLinkPath: string, playStoreURL: string) => {
67+
return (
68+
`intent://${deepLinkPath}` +
69+
'#Intent;' +
70+
'scheme=tuk-app;' +
71+
'package=com.plottwist.tuk;' +
72+
`S.browser_fallback_url=${encodeURIComponent(playStoreURL)};` +
73+
'end'
74+
);
75+
};
76+
77+
const appendIOSIframe = (targetDeepLink: string) => {
78+
let timer: number | null = null;
79+
80+
const cleanup = () => {
81+
if (timer) {
82+
window.clearTimeout(timer);
83+
timer = null;
84+
}
85+
86+
document.removeEventListener('visibilitychange', onHide, true);
87+
window.removeEventListener('pagehide', onHide, true);
88+
window.removeEventListener('blur', onHide, true);
89+
const iframe = document.getElementById('__dl_iframe__');
90+
if (iframe && iframe.parentNode) iframe.parentNode.removeChild(iframe);
91+
};
92+
93+
const onHide = () => cleanup();
94+
95+
document.addEventListener('visibilitychange', onHide, true);
96+
window.addEventListener('pagehide', onHide, true);
97+
window.addEventListener('blur', onHide, true);
98+
99+
const iframe = document.createElement('iframe');
100+
iframe.style.display = 'none';
101+
iframe.id = '__dl_iframe__';
102+
iframe.src = targetDeepLink;
103+
document.body.appendChild(iframe);
104+
105+
timer = window.setTimeout(() => {
106+
try {
107+
window.location.replace(IOS_STORE_URL_APPS);
108+
} catch {
109+
window.location.replace(IOS_STORE_URL_WEB);
110+
} finally {
111+
cleanup();
112+
}
113+
}, FALL_BACK_DELAY_MS);
114+
115+
return;
116+
};

0 commit comments

Comments
 (0)