회사 프로젝트 오픈 베타를 앞두고 테스트 기간 중에 중요도가 높은 이슈가 발생했다.
Safari 브라우저에서 PASS 본인인증 팝업이 뜨지 않는 이슈였다.
중요도가 높았던 이유는 회원가입이나 요금제를 신청하거나 마이페이지에서 수정할 때 본인인증이 필수 프로세스이며 아이폰, 맥북에서 Safari를 주로 사용하는 사용자들도 있기 때문이다.
Safari에서 본인인증 버튼을 클릭했을 때 팝업이 차단되었다
는 문구가 떴다. 맥북에서는 해당 문구를 확인하고 차단된 팝업을 허용하면 되지만, 아이폰에서는 버튼을 클릭했을 때 아무런 동작을 하지 않아서 사용자가 왜 안되는지 알 수 없는 상태였다.
PASS 본인인증 프로세스는 간단하게 설명하면 다음처럼 진행된다.
대부분의 복잡한 인증 로직은 서버에서 처리해주므로 프론트에서 처리할건 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'
)
...