본문 바로가기

IT/웹 개발

인앱 브라우저에서 외부 브라우저 열기

 

카카오톡 인앱 브라우저를 통해 접속한 유저가 카카오톡 본인인증 기능을 사용할 때 오류가 발생한다는 제보를 받았습니다.

 

직접 테스트했을 때 오류가 재현되진 않았지만, 인앱 브라우저에서 휴대폰 본인인증(PASS)을 진행하면 세션 데이터가 날아가는 버그를 겪고 있었기 때문에 인앱 브라우저를 외부 브라우저로 여는 방법을 찾기 시작했습니다.


그러다 아래 블로그를 발견했습니다.

 

🔗 카카오톡/라인 인앱브라우저에서 외부브라우저 띄우기

 

이분 역시 인앱 브라우저를 벗어나길 원하셨고, 여러 해결 방법을 블로그에 기록하셨습니다.

저와 같은 전국 각지에서 고군분투하는 개발자들을 위해, 전체 소스코드도 제공하여 커스터마이징할 수 있도록 해주셨습니다.


해당 코드를 검토한 후, 코드 리팩토링을 진행했습니다.

execCommand()가 지원 중단되었기 때문에 Clipboard API로 대체하는 작업도 진행했습니다.

 

🔗 execCommand() 지원 중단

🔗 Clipboard API

 

class InApp {
    #userAgent;
    #targetUrl;
    #browserPatterns;

    constructor() {
        this.#userAgent = navigator.userAgent.toLowerCase();
        this.#targetUrl = location.href;
        this.#browserPatterns = {
            kakaotalk: /kakaotalk/i,
            line: /line/i,
            otherInApp: /inapp|naver|snapchat|wirtschaftswoche|thunderbird|instagram|everytimeapp|whatsApp|electron|wadiz|aliapp|zumapp|iphone(.*)whale|android(.*)whale|kakaostory|band|twitter|DaumApps|DaumDevice\/mobile|FB_IAB|FB4A|FBAN|FBIOS|FBSS|trill|SamsungBrowser\/[^1]/i,
            ios: /iphone|ipad|ipod/i
        };

        this.#inAppDenyExecJs(this.#openOutLink.bind(this));
    }

    #inAppDenyExecJs = (callback) => {
        document.readyState !== 'loading'
            ? callback()
            : document.addEventListener('DOMContentLoaded', callback);
    }

    #openOutLink() {
        const browserHandlers = {
            kakaotalk: () => this.#openKakaoTalk(),
            line: () => this.#openLine(),
            otherInApp: () => this.#handleOtherInApp()
        };

        for (const [browser, handler] of Object.entries(browserHandlers)) {
            if (this.#browserPatterns[browser].test(this.#userAgent)) {
                return handler();
            }
        }
    }

    #openKakaoTalk() {
        location.href = `kakaotalk://web/openExternal?url=${encodeURIComponent(this.#targetUrl)}`;
    }

    #openLine() {
        const separator = this.#targetUrl.includes('?') ? '&' : '?';
        location.href = `${this.#targetUrl}${separator}openExternalBrowser=1`;
    }

    #handleOtherInApp() {
        this.#browserPatterns.ios.test(this.#userAgent)
            ? this.#handleIOS()
            : this.#handleAndroid();
    }

    #handleAndroid() {
        location.href = `intent://${this.#targetUrl.replace(/https?:\/\//i, '')}#Intent;scheme=http;package=com.android.chrome;end`;
    }

    #handleIOS() {
        this.#setMobileViewport();
        this.#setNotoSansFont();
        this.#setIOSContent();
    }

    #setMobileViewport() {
        const mobile = document.createElement('meta');
        mobile.name = 'viewport';
        mobile.content = 'width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, minimal-ui';
        document.head.appendChild(mobile);
    }

    #setNotoSansFont() {
        const fonts = document.createElement('link');
        fonts.href = 'https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap';
        document.head.appendChild(fonts);
    }

    #setIOSContent() {
        const content = `
            <style>
                body {
                    width: 100%
                    height: 100%;
                    font-family: 'Noto Sans KR', sans-serif;
                    margin: 0;
                    padding: 0;
                    overflow: hidden;
                }

                article {
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                }
            </style>
            <h2 style='padding-top: 50px; text-align: center; font-family: "Noto Sans KR", sans-serif;'>
                인앱 브라우저 호환성 문제로 인해<br>
                Safari로 접속해야합니다.
            </h2>
            <article style='text-align: center; font-size: 17px; word-break: keep-all; color: #999;'>
                아래 버튼을 눌러 Safari를 실행해주세요<br>
                Safari가 열리면, 주소창을 길게 터치한 후<br>
                '붙여놓기 및 이동'을 누르면<br>
                정상적으로 이용할 수 있습니다.<br><br>
                <button id="safariButton" style='min-width: 180px; margin-top: 10px; height: 54px; font-weight: 700; background-color: #31408E; color: #fff; border-radius: 4px; font-size: 17px; border: 0;'>
                    Safari로 열기
                </button>

                <img src="/img/in-app_safari.jpeg" alt="붙여넣기 및 이동" style='width: 70%; margin-top: 50px;'>
            </article>
        `;

        document.body.innerHTML = content;

        document.getElementById('safariButton').onclick = () => this.#inAppBrowserOut();
    }

    async #inAppBrowserOut() {
        try {
            const response = await this.#copyToClipboard(window.location.href);

            if (response) {
                alert('URL주소가 복사되었습니다.\n\nSafari가 열리면 주소창을 길게 터치한 뒤, "붙여놓기 및 이동"를 누르면 정상적으로 이용하실 수 있습니다.');
                location.href = 'x-web-search://?';
            }
        } catch (error) {
            console.error('Error:', error);
        }
    };

    async #copyToClipboard(copyText) {
        if (!document.hasFocus()) {
            console.error('문서가 포커스되지 않았습니다. 클립보드에 쓸 수 없습니다.');
            return false;
        }

        try {
            // Clipboard API
            await navigator.clipboard.writeText(copyText);
            return true;
        } catch (error) {
            console.error('Clipboard API error:', error);
            return false;
        }
    };
}

 

InApp.zip
0.03MB