Skip to content

Commit ead81ab

Browse files
committed
feat: 인앱 브라우저 감지
- InAppBrowserDetect 컴포넌트 추가: 인앱 브라우저에서의 기능 제한 안내 및 외부 브라우저로 이동 기능 구현 - useInAppBrowserDetect 훅 추가: 인앱 브라우저 감지 및 관련 기능 제공 - RouteProvider에 InAppBrowserDetect 컴포넌트 통합
1 parent 87a54fd commit ead81ab

File tree

4 files changed

+198
-5
lines changed

4 files changed

+198
-5
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import useInAppBrowserDetect from '@/hooks/useInAppBrowserDetect';
2+
import { ReactNode, useEffect } from 'react';
3+
4+
interface InAppBrowserDetectProps {
5+
children: ReactNode;
6+
}
7+
8+
const InAppBrowserDetect = ({ children }: InAppBrowserDetectProps) => {
9+
const {
10+
isInAppBrowser,
11+
browserName,
12+
moveToExternalBrowser,
13+
moveToStore,
14+
isAndroid,
15+
isIOS,
16+
} = useInAppBrowserDetect();
17+
18+
useEffect(() => {
19+
if (isInAppBrowser) moveToExternalBrowser();
20+
}, [isInAppBrowser, moveToExternalBrowser]);
21+
22+
if (isInAppBrowser) {
23+
const userBrowserName = (() => {
24+
switch (browserName) {
25+
case 'kakaotalk':
26+
return '카카오톡';
27+
case 'naver':
28+
return '네이버';
29+
case 'facebook':
30+
return '페이스북';
31+
case 'instagram':
32+
return '인스타그램';
33+
case 'line':
34+
return '라인';
35+
default:
36+
return '현재 사용중인';
37+
}
38+
})();
39+
40+
const storeName = (() => {
41+
if (isAndroid) return '구글 플레이';
42+
if (isIOS) return '앱 스토어';
43+
return '스토어';
44+
})();
45+
46+
return (
47+
<div>
48+
<div>
49+
<p>{userBrowserName} 인앱브라우저에서는</p>
50+
<p>일부 기능 이용이 제한됩니다.</p>
51+
<p>외부 브라우저로 이동중입니다...</p>
52+
<button
53+
onClick={(e) => {
54+
e.preventDefault();
55+
moveToStore();
56+
}}
57+
>
58+
{storeName}에서 설치하기
59+
</button>
60+
</div>
61+
</div>
62+
);
63+
}
64+
65+
return <>{children}</>;
66+
};
67+
68+
export default InAppBrowserDetect;

apps/web/src/hooks/.gitkeep

Whitespace-only changes.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { useCallback } from 'react';
2+
3+
interface InAppBrowserInfo {
4+
isInAppBrowser: boolean;
5+
browserName: string | null;
6+
}
7+
8+
interface BrowserPattern {
9+
name: string;
10+
patterns: string[];
11+
}
12+
13+
const BROWSER_PATTERNS: BrowserPattern[] = [
14+
{
15+
name: 'kakaotalk',
16+
patterns: ['kakaotalk'],
17+
},
18+
{
19+
name: 'naver',
20+
patterns: ['naver', 'naver.*inapp', 'naver.*whale'],
21+
},
22+
{
23+
name: 'facebook',
24+
patterns: ['fban', 'fbios', 'fb_iab'],
25+
},
26+
{
27+
name: 'instagram',
28+
patterns: ['instagram', 'instagram.*inapp'],
29+
},
30+
{
31+
name: 'line',
32+
patterns: ['line', 'line.*inapp'],
33+
},
34+
];
35+
36+
// 테스트 용 (실제로는 우리의 앱 정보를 사용해야 함)
37+
const NAVER_APP_INFO = {
38+
androidPackage: 'com.nhn.android.search',
39+
iosAppId: '393499958',
40+
};
41+
42+
const useInAppBrowserDetect = () => {
43+
const userAgent = navigator.userAgent.toLowerCase();
44+
const isAndroid = userAgent.includes('android');
45+
const isIOS = /iphone|ipad|ipod/.test(userAgent);
46+
47+
const detectInAppBrowser = (): InAppBrowserInfo => {
48+
for (const browser of BROWSER_PATTERNS) {
49+
if (
50+
browser.patterns.some((pattern) =>
51+
pattern.includes('.*')
52+
? new RegExp(pattern).test(userAgent)
53+
: userAgent.includes(pattern),
54+
)
55+
) {
56+
return { isInAppBrowser: true, browserName: browser.name };
57+
}
58+
}
59+
60+
return { isInAppBrowser: false, browserName: null };
61+
};
62+
63+
const { isInAppBrowser, browserName } = detectInAppBrowser();
64+
65+
const moveToStore = useCallback(() => {
66+
if (isAndroid) {
67+
window.location.href = `market://details?id=${NAVER_APP_INFO.androidPackage}`;
68+
} else if (isIOS) {
69+
window.location.href = `itms-apps://itunes.apple.com/app/id${NAVER_APP_INFO.iosAppId}`;
70+
} else {
71+
// 폴백 URL (웹 버전)
72+
window.location.href = isAndroid
73+
? `https://play.google.com/store/apps/details?id=${NAVER_APP_INFO.androidPackage}`
74+
: `https://apps.apple.com/app/id${NAVER_APP_INFO.iosAppId}`;
75+
}
76+
}, [isAndroid, isIOS]);
77+
78+
const getExternalBrowserUrl = useCallback(
79+
(currentUrl: string, browser: string): string => {
80+
const isAndroid = userAgent.includes('android');
81+
82+
if (browser === 'kakaotalk') {
83+
return `kakaotalk://web/openExternal?url=${encodeURIComponent(currentUrl)}`;
84+
}
85+
86+
if (isAndroid) {
87+
return `intent://${currentUrl.replace(/https?:\/\//, '')}#Intent;scheme=https;package=com.android.chrome;end`;
88+
}
89+
90+
return currentUrl;
91+
},
92+
[userAgent],
93+
);
94+
95+
const moveToExternalBrowser = useCallback(() => {
96+
const currentUrl = window.location.href;
97+
98+
if (!isInAppBrowser) return;
99+
100+
if (browserName) {
101+
window.location.href = getExternalBrowserUrl(currentUrl, browserName);
102+
return;
103+
}
104+
105+
const newWindow = window.open(currentUrl, '_blank');
106+
107+
if (!newWindow) {
108+
window.location.href = currentUrl;
109+
}
110+
}, [isInAppBrowser, browserName, getExternalBrowserUrl]);
111+
112+
return {
113+
isInAppBrowser,
114+
browserName,
115+
moveToExternalBrowser,
116+
moveToStore,
117+
isAndroid,
118+
isIOS,
119+
};
120+
};
121+
122+
export default useInAppBrowserDetect;

apps/web/src/routes/RouteProvider.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PrivateRoute } from './PrivateRoute';
1414
import { PublicRoute } from './PublicRoute';
1515
import DefaultComponent from '@/components/DefaultComponent';
1616
import LandingPage from '@/pages/Landing';
17+
import InAppBrowserDetect from '@/components/InAppBrowser';
1718

1819
type ROUTE_TYPE = 'PRIVATE' | 'PUBLIC';
1920

@@ -29,11 +30,13 @@ const router = createBrowserRouter([
2930
{
3031
path: PATH.ROOT,
3132
element: (
32-
<UnknownErrorBoundary>
33-
<APIErrorBoundary>
34-
<Outlet />
35-
</APIErrorBoundary>
36-
</UnknownErrorBoundary>
33+
<InAppBrowserDetect>
34+
<UnknownErrorBoundary>
35+
<APIErrorBoundary>
36+
<Outlet />
37+
</APIErrorBoundary>
38+
</UnknownErrorBoundary>
39+
</InAppBrowserDetect>
3740
),
3841
errorElement: <SomethingWentWrong />,
3942
children: [

0 commit comments

Comments
 (0)