feat(webclient): add in-app browser detection and redirect to external browser

Implement JavaScript logic to detect popular in-app browsers (KakaoTalk, Naver, Facebook, Instagram, Line) on Android and iOS, and redirect users to external browsers. This ensures compatibility with service workers and other web features that may not function properly within in-app browsers, improving overall user experience and functionality of the Portal Proxy Gateway.

lemon-mint committed Nov 1, 2025 at 15:58 UTC 0cedc70aa86247ce2469cfbbc42ff1a931ee7cf0
1 file changed +72 -17
cmd/webclient/index.html
+72 -17
@@ -6,27 +6,66 @@
6 <title>Portal Proxy Gateway</title>
7 </head>
8 <body>
9 + <script>
10 + // In-app browser detection and redirect handler
11 + (function() {
12 + const userAgent = navigator.userAgent.toLowerCase();
13 + const finalUrl = window.location.href;
14 +
15 + // Detect various in-app browsers
16 + const isKakao = /kakaotalk/i.test(userAgent);
17 + const isNaver = /naver/i.test(userAgent);
18 + const isFacebook = /fb|fbav|fban/i.test(userAgent);
19 + const isInstagram = /instagram/i.test(userAgent);
20 + const isLine = /line/i.test(userAgent);
21 + const isInAppBrowser = isKakao || isNaver || isFacebook || isInstagram || isLine;
22 + const isAndroid = /android/.test(userAgent);
23 + const isIOS = /ipad|iphone|ipod/.test(userAgent);
24 +
25 + if (!isInAppBrowser) {
26 + return; // Not in-app browser, proceed normally
27 + }
28 +
29 + // Handle redirection to external browser
30 + if (isAndroid) {
31 + if (isKakao) {
32 + location.href = 'kakaotalk://web/openExternal?url=' + encodeURIComponent(finalUrl);
33 + } else {
34 + // Use Intent to open in Chrome or default browser
35 + const intentUrl = 'intent://' + finalUrl.replace(/^https?:\/\//, '') + '#Intent;scheme=https;action=android.intent.action.VIEW;end';
36 + location.href = intentUrl;
37 + }
38 + return; // Stop execution
39 + } else if (isIOS) {
40 + if (isKakao) {
41 + location.href = 'kakaotalk://web/openExternal?url=' + encodeURIComponent(finalUrl);
42 + // Set up auto-close listener for iOS KakaoTalk
43 + document.addEventListener("visibilitychange", () => {
44 + if(document.visibilityState == "visible") {
45 + location.href = 'kakaoweb://closeBrowser';
46 + }
47 + });
48 + } else {
49 + // For other iOS in-app browsers, try to open in Safari
50 + // Display a message to user with a link
51 + document.body.innerHTML = `
52 + <div style="padding: 20px; text-align: center; font-family: sans-serif;">
53 + <h2>Open in External Browser</h2>
54 + <p>This page needs to be opened in an external browser.</p>
55 + <p><a href="${finalUrl}" target="_blank" style="display: inline-block; margin: 20px 0; padding: 15px 30px; background: #007AFF; color: white; text-decoration: none; border-radius: 8px; font-size: 16px;">Open in Safari</a></p>
56 + <p style="font-size: 14px; color: #666;">Or select "Open in Safari" or "Open in External Browser" from the menu in the upper right corner.</p>
57 + </div>
58 + `;
59 + }
60 + return; // Stop execution
61 + }
62 + })();
63 + </script>
64 <h1>Portal Proxy Gateway</h1>
65 <hr/>
66 <p id="status">Please wait for the service worker to register...</p>
67 <script>
68 async function registerServiceWorker() {
14 - try {
15 - if (!('serviceWorker'in navigator)) {
16 - throw new Error('This browser does not support Service Worker.');
17 - }
18 -
19 - showStatus('Registering Service Worker...', 'loading');
20 -
21 - // Register service worker
22 - const registration = await navigator.serviceWorker.register('/service-worker.js', {
23 - scope: '/'
24 - });
25 - showStatus('Service Worker registered successfully, Wait a moment...', 'success');
26 - } catch (error) {
27 - showStatus('Service Worker registration failed: ' + error.message, 'error');
28 - }
29 -
69 const reloadFn = async () => {
70 const resp = await fetch('/e8c2c70c-ec4a-40b2-b8af-d5638264f831');
71 const text = await resp.text();
@@ -42,7 +81,23 @@
81 }, 1000);
82 }
83 }
45 - reloadFn();
84 +
85 + try {
86 + if (!('serviceWorker'in navigator)) {
87 + throw new Error('This browser does not support Service Worker.');
88 + }
89 +
90 + showStatus('Registering Service Worker...', 'loading');
91 +
92 + // Register service worker
93 + const registration = await navigator.serviceWorker.register('/service-worker.js', {
94 + scope: '/'
95 + });
96 + showStatus('Service Worker registered successfully, Wait a moment...', 'success');
97 + setTimeout(reloadFn, 100);
98 + } catch (error) {
99 + showStatus('Service Worker registration failed: ' + error.message, 'error');
100 + }
101 }
102
103 function showStatus(message, status) {