Safari 브라우저 window.open 팝업 차단 이슈

Taemin Jang·2024년 10월 29일
0

회사 프로젝트 오픈 베타를 앞두고 테스트 기간 중에 중요도가 높은 이슈가 발생했다.

Safari 브라우저에서 PASS 본인인증 팝업이 뜨지 않는 이슈였다.

중요도가 높았던 이유는 회원가입이나 요금제를 신청하거나 마이페이지에서 수정할 때 본인인증이 필수 프로세스이며 아이폰, 맥북에서 Safari를 주로 사용하는 사용자들도 있기 때문이다.

Safari에서 본인인증 버튼을 클릭했을 때 팝업이 차단되었다는 문구가 떴다. 맥북에서는 해당 문구를 확인하고 차단된 팝업을 허용하면 되지만, 아이폰에서는 버튼을 클릭했을 때 아무런 동작을 하지 않아서 사용자가 왜 안되는지 알 수 없는 상태였다.

원인 파악

PASS 본인인증 프로세스는 간단하게 설명하면 다음처럼 진행된다.

  1. 클라이언트로부터 이벤트 발생 시 서버에게 본인인증 html 파일을 요청
  2. 전달받은 html 파일을 팝업창으로 띄움
  3. 본인인증을 마친 클라이언트의 결과값을 콜백으로 전달받아 결과에 맞게 처리

대부분의 복잡한 인증 로직은 서버에서 처리해주므로 프론트에서 처리할건 html 파일을 팝업창으로 보여주는 것 뿐이다.

팝업창을 띄우는 코드는 다음과 같다.

const openPassPopup = async (action = 'signup') => {
    try {
        const res = await fetch('서버 API')
        return new Promise((resolve, reject) => {
            if (res.code === 200 && res.data) {
                const listener = (event) => {
                    window.removeEventListener('message', listener)
                    if (event?.data?.message === 'PassPopupClosed') {
                        resolve(event.data.result)
                    } else {
                        reject(null)
                    }
                }
                window.removeEventListener('message', listener)
                window.addEventListener('message', listener, false)
                const authWindow = window.open(
                    '',
                    '_blank',
                    'width=500, height=550, top=0, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, location=no, scrollbar=no'
                )
                if (authWindow) {
                    authWindow.document.open()
                    authWindow.document.write(data)
                    authWindow.document.close()
                }
            } else {
                reject(null)
            }
        })
    } catch (error) {
        throw error
    }
}

위 코드를 간단하게 하면 아래 코드처럼 동작하고, 이는 사파리 브라우저에서 정상적으로 동작하지 않게 된다.

const openPassPopup = async (action = 'signup') => {
  await 비동기
  window.open('', '_blank') // 동작 안함
  // window.open(url, target, windowFeatures)
}

그 이유는 사파리 브라우저에서는 비동기 호출 후 이루어지는 window.open() 호출을 차단하기 때문이다.

사파리에서는 사용자 클릭 이벤트에서만 window.open을 통한 팝업을 허용하며, 보안 정책 중 하나로 script의 팝업 호출을 제한하고 있다.

해결 방법

비동기 호출 하기 전에 window.open을 호출하고 전달 받은 html 파일을 보여주는 방식으로 해결했다.

const openPassPopup = async (action = 'signup') => {
    try {
      	const authWindow = window.open(
            '',
            '_blank',
            'width=500, height=550, top=0, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, location=no, scrollbar=no'
        )
        const res = await fetch('서버 API')
        return new Promise((resolve, reject) => {
            if (res.code === 200 && res.data) {
                ...
                
                if (authWindow) {
                    authWindow.document.open()
                    authWindow.document.write(data)
                    authWindow.document.close()
                }
            } else {
                reject(null)
            }
        })
    } catch (error) {
        throw error
    }
}

이렇게 팝업창을 미리 띄우고, api 호출로 받아온 데이터를 받아온다.

띄운 팝업창에 document.open()으로 기존에 있던 페이지 내용을 지우고 document.write(data)로 받아온 html 파일로 내용을 작성한 뒤 document.close()로 페이지를 완성함으로써 추가로 write를 할 수 없는 상태이다.

추가 이슈 사항들

window.open할 때 target을 '_blank'로 작성하고 팝업을 띄우면, 버튼을 클릭할 때마다 중복된 팝업이 계속 뜨는 버그가 발생하게 된다.

MDN에서도 target='_blank'는 피하는 것을 권고하고 있다.

그래서 다음과 같이 수정하여 해결했다.

const authWindow = window.open(
    '',
    'passPopup',
    'width=500, height=550, top=0, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, location=no, scrollbar=no'
)

이렇게 함으로써 중복으로 뜨는 이슈는 해결했지만, 현재 창이 떠있을 때 한 번 더 클릭하면 에러가 발생하게 된다.

이를 해결하기 위해 팝업창이 있으면 닫고 다시 띄우도록 수정해줬다.

let authWindow: Window | null

if (authWindow) {
  authWindow.close()
}

authWindow = window.open(
    '',
    'passPopup',
    'width=500, height=550, top=0, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, location=no, scrollbar=no'
)

...
profile
하루하루 공부한 내용 기록하기

0개의 댓글

관련 채용 정보