로그인 이슈 웹뷰
트러블 슈팅 과정 공유
이런 이슈가 있었습니다.
- 환경: iOS (웹뷰)
- 유저 상태: 본인 명의로 엔카 회원 가입이 되어있고, 네이버 간편가입은 안되어있음
- 정상동작: duplicate 페이지로 리다이렉트 되어야되는 상황, 하지만 login 페이지로 리다이렉트 되는 이슈
- 발생빈도: 10번 중에 1번꼴, 10% 확률로 발생
- 환경: iOS (웹뷰)
- 유저 상태: 본인 명의로 엔카 회원 가입이 되어있고, 네이버 간편가입은 안되어있음
- 정상동작: duplicate 페이지로 리다이렉트 되어야되는 상황, 하지만 login 페이지로 리다이렉트 되는 이슈
- 발생빈도: 10번 중에 1번꼴, 10% 확률로 발생

APP 에서 로그인 시도
/join/simple/connect
?platform=NAVER&token=${token}
1. 유저가 요청한 platform (ex: 네이버)에서 token값을 받아와서 아래 웹뷰를 open

웹뷰에서 라우팅 과정 <FE>
1. /join/simple/connect?platform=NAVER&token=${token}
2. /callback/NAVER?tokenFromApp=${token}
3. /join/sns/duplicate
웹뷰에서 라우팅 과정 <FE>
1. /join/simple/connect?platform=NAVER&token=${token}
JoinSnsConnectPage.js
1. 유저의 SNS 정보 조회API 호출(getSnsUserProfile)
2. 해당 결과를 JOIN_DATA 세션 스토리지에 저장 (이름, 이메일, 생년월일, 성별)
3. /callback/NAVER 페이지로 이동시킴
2. /callback/NAVER?tokenFromApp=${token}
웹뷰에서 라우팅 과정 <FE>
2. /callback/NAVER?tokenFromApp=${token}
SnsCallbackPage.js
1.initSnsCallback
2.initApp
3.handleVerifyToken
4.loginSns
5.handleErrorRedirect
6.getSnsUserProfile
3. /join/sns/duplicate
토큰 검증
로그인 시도
로그인 시도 실패 처리(ex: 회원가입)
sns 유저 정보 조회
- 환경: iOS (웹뷰)
- 유저 상태: 본인 명의로 엔카 회원 가입이 되어있고, 네이버 간편가입은 안되어있음
- 정상동작: duplicate 페이지로 리다이렉트 되어야되는 상황, 하지만 login 페이지로 리다이렉트 되는 이슈
- 발생빈도: 10번 중에 1번꼴, 10% 확률로 발생
웹뷰 디버깅 시작

1️⃣ console.log()를 활용할 수 없다
2️⃣ alert()를 사용하면 이슈가 재현되지 않는다
3️⃣ 로컬 서버를 활용할 수 없다
4️⃣ APP 레이어에서 어떤 일이 일어나는지 알기 어렵다

👈🏻 실패한 케이스 /user/socials/NAVER/me API가 두번 호출됨 (getSnsUserProfile)
👉🏻 성공한 케이스 /user/socials/NAVER/me API가 한번 호출됨 (getSnsUserProfile)
웹뷰에서 API 호출 히스토리
웹뷰 디버깅 시작
모든 걸 의심해야함



/join/simple/connect?platform=NAVER&token=${token}
container 컴포넌트 안에 4개의 effect가 있음






2️⃣
1️⃣
3️⃣
4️⃣
5️⃣
6️⃣
1. 3번 useEffect의 Callback 함수
8️⃣
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
4. 4번 useEffect의 Callback 함수
5. if문의 setSessionItem 두가지
6. history.replace('/callback/~')
7. callback 페이지의 로직 실행(동기 로직)
8. getSnsUserProfile Promise 객체의
fullfiled callback 실행

1. 3번 useEffect의 Callback 함수
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
4. 4번 useEffect의 Callback 함수
5. if문의 setSessionItem 두가지
6. history.replace('/callback/~')
7. callback 페이지의 로직 실행(동기 로직)
8. getSnsUserProfile Promise 객체의
fullfiled callback 실행



Ajax (XML Http Request)
8. Promise Fulfill 콜백 실행
Promise 내부 동작


Promise 내부 동작


then의 콜백함수
catch의 콜백함수
pending
false
Promise 내부 동작


then의 콜백함수
catch의 콜백함수
pending
false

Promise 내부 동작


then의 콜백함수
catch의 콜백함수
fulfilled
true



1. 3번 useEffect의 Callback 함수
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
4. 4번 useEffect의 Callback 함수
5. if문의 setSessionItem 두가지
6. history.replace('/callback/~')
7. callback 페이지의 로직 실행(동기 로직)
8. getSnsUserProfile Promise 객체의
fullfiled callback 실행



Ajax (XML Http Request)
8. Promise Fulfill 콜백 실행



Promise 처리
Promise Fulfill 콜백
1. 3번 useEffect의 Callback 함수
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
4. 4번 useEffect의 Callback 함수
5. if문의 setSessionItem 두가지
6. history.replace('/callback/~')
7. callback 페이지의 로직 실행(동기 로직)
8. getSnsUserProfile Promise 객체의
fullfiled callback 실행
SnsCallbackPage.js
1.initSnsCallback
2.initApp
3.handleVerifyToken
4.loginSns
5.handleErrorRedirect
6.getSnsUserProfile
토큰 검증
로그인 시도
로그인 시도 실패 처리(ex: 회원가입)
sns 유저 정보 조회
async
async
async
async
Race Condition

여러 API를 순서대로 호출하더라도
그 결과는 보장되지 않는다.
new Promise.race([p1, p2, p3])
심지어 같은 API를 시간차를 두고 호출해도
그 순서 역시 보장되지 않는다.
1. 3번 useEffect의 Callback 함수
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
4. 4번 useEffect의 Callback 함수
5. if문의 setSessionItem 두가지
6. history.replace('/callback/~')
7. callback 페이지의 로직 실행(동기 로직)
8. getSnsUserProfile Promise 객체의
fullfiled callback 실행
SnsCallbackPage.js
1.initSnsCallback
2.initApp
3.handleVerifyToken
4.loginSns
5.handleErrorRedirect
6.getSnsUserProfile
토큰 검증
로그인 시도
로그인 시도 실패 처리(ex: 회원가입)
sns 유저 정보 조회
async
async
async
1️⃣
2️⃣
async
3️⃣
4️⃣
3번 getSnsUserProfile()
VS
3. handleVerifyToken
4. loginSns
6. getSnsUserProfile
3번 getSnsUserProfile()
VS
3. handleVerifyToken
4. loginSns
6. getSnsUserProfile
A getSnsUserProfile() 호출
B getSnsUserProfile() 호출
Finish
A getSnsUserProfile() 호출
B getSnsUserProfile() 호출
Finish
이슈 상황
getSnsUserProfile 비동기 요청(A)이
이동된 페이지(/callback)에서 호출한 getSnsUserProfile
비동기 요청(B) 보다 늦게 Fullfilled 되는 경우
A getSnsUserProfile() 호출
B getSnsUserProfile() 호출
Finish
이슈 상황
getSnsUserProfile 비동기 요청(A)이
이동된 페이지(/callback)에서 호출한 getSnsUserProfile
비동기 요청(B) 보다 늦게 Fullfilled 되는 경우



B Promise
A Promise
B Fulfill 콜백
B Fulfill 콜백

A getSnsUserProfile()

B getSnsUserProfile()

duplicate 페이지의 useEffect
Root Cause는?
A getSnsUserProfile() 호출
B getSnsUserProfile() 호출
Finish
Root Cause는?
1. 3번 useEffect의 Callback 함수
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
4. 4번 useEffect의 Callback 함수
5. if문의 setSessionItem 두가지
6. history.replace('/callback/~')
7. callback 페이지의 로직 실행(동기 로직)
8. getSnsUserProfile Promise 객체의
fullfiled callback 실행
async
/join/simple/connect?platform=NAVER&token=${token}
container 컴포넌트 안에 4개의 effect가 있음
Root Cause는?
1. 3번 useEffect의 Callback 함수
2. setSessionItem()
3. getSnsUserProfile() 👉🏻 Promise 객체 생성
5. 4번 useEffect의 Callback 함수
6. if문의 setSessionItem 두가지
7. history.replace('/callback/~')
8. callback 페이지의 로직 실행(동기 로직)
4. getSnsUserProfile Promise 객체의
fullfiled callback 실행
async
/join/simple/connect?platform=NAVER&token=${token}
container 컴포넌트 안에 4개의 effect가 있음

🤔 재발 방지를 위해서는?
⚠️ useEffect 추가할때 필수 고려사항
✅ 라우트 변경(ex: router.replace(), router.push())시 heap 메모리, 실행 컨텍스트는 초기화 되지 않는다
✅ effect에 비동기 동작이 있다면 다른 effect와 실행 순서가 어떻게 되는지 반드시 검토하자
✅ effect를 어떻게 묶을지 그룹핑을 신경쓰자
✅ 비동기 동작을 정확하게 이해하자
deck
By Sang Jin Lee
deck
- 1