로그인 이슈 웹뷰

트러블 슈팅 과정 공유

이런 이슈가 있었습니다.

- 환경: 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. 4useEffect의 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. 4useEffect의 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. 4useEffect의 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. 4useEffect의 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. 4useEffect의 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. 4useEffect의 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. 4useEffect의 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