[일단냥] 일단냥 웹앱 적용기 (ReactNative, Next.js)

Subin·2023년 4월 19일
63
post-thumbnail

0. 일단냥에 웹앱을 적용한 이유

🚧 현재 일단냥은 리워크 예정이므로 더 나은 퀄리티를 위해 커뮤니티는 폐쇄중입니다.

커뮤니티 메인게시글 보기
Apple iPhone 11 Pro MaxApple iPhone 11 Pro Max Screenshot 5

일단냥은 아래의 링크에서 다운받을 수 있다.
IOS
Android

현재 일단냥 앱은 주요 기능인 단어장 기능밖에 없는 상태이며 이는 서버가 없는 그냥 정적인 화면으로 구성되어 있다.

이후로 구현할 기능은 커뮤니티 게시판, 게시판에서 AI가 댓글 달아주기.. 등등 서버가 필요한 작업이 있었다. TMI지만 커뮤니티를 만들고 싶은 이유가 일본어를 공부하면서 궁금한 것들을 질문하거나 이런 일본어 공부에 공통된 유저가 소통할 수 있는 장소가 있으면 혼자 공부하지 않는구나 라고 외롭지 않을거라고 생각했다. 무엇보다 AI에게 질문 게시글을 올리면 자동으로 답변을 댓글로 달아주는 기능을 구상하고 있었다.

무려 네이버앱이나 토스앱은 웹뷰로 만들었다고도 하고 나는 웹개발자를 지향하기에 일단냥앱에도 웹뷰를 적용해보고 싶었다. 그리고 웹앱을 만들어 본 적도 없었기도 해서 경험해보고 싶었다.

무엇보다 웹뷰를 적용하게 되면 AOS, IOS 앱을 배포하고 심사를 대기하지 않아도 웹의 업데이트 내용을 배포만 하면 되기에 심사를 거치지 않아도 돼서 가장 큰 장점이라고 생각한다. 즉 배포에 자유로워지고 더불어 AOS, IOS 각 OS 맞게 기능을 구현하고 테스트를 따로 하지 않아도 된다는 점이 매우 편리하다.

간단하게 정리하자면 아래와 같다.

웹뷰를 적용하면서 얻게되는 이점
1. AOS, IOS 각각 기능을 구현하기 보다 웹 하나만 구현하면 되기에 개발 자원이 크게 줄어든다.
2. 배포함에 있어 스토어에서 심사를 받지 않고 곧바로 업데이트가 가능하다.
3. 1번과 비슷한데 기기간 호환성과 기기간의 구현 기술이 웹하나로 인해 통합된다.

1. 웹뷰에서 OAuth 구현하기

1.1 사용된 기술

먼저 일단냥에서 사용된 앱, 웹, 서버가 무엇인지 알아보자

  • 앱 : ReactNative
  • 웹 : Next.JS
  • 웹 서버 : Next.JS의 API Route (Firebase API 요청을 위함)
  • 서버리스 : Firebase

1.2 OAuth 사용 이유

일단냥에서는 카카오 로그인을 채택했다. 먼저 OAuth를 채택한 이유는 1초 간편 로그인으로 사용자들이 회원가입에 들이는 수고를 없애 만족감을 주고 또 더 간단히 가입이 가능하기에 많은 사용자들을 끌여들이는 효과도 있기 때문이다.
아래는 한국 소비자 연맹의 조사 자료이다.

1.2.1 OAuth에서 카카오를 채택한 이유

한국 소비자 연맹에서 조사한 바에 따르면소셜로그인 시 주로 사용하는 소셜미디어 1순위는 네이버51.2%로 가장 많았고, 카카오39.8%로 나타나 소셜로그인 시 국내 소셜미디어 계정을 가장 많이 이용하고 있는 것으로 확인되었는데 일단냥에서는 네이버로 로그인하지 왜 카카오를 채택했을까?

일단 내 주위에 카카오톡을 이용하지 않는 사람은 본적이 없다. 그 말은 이미 한국에서는 모든 사람이 카카오톡 계정이 있는 것이다. 반대로 네이버는 계정이 없는 사람도 꽤 봤어서, 나는 더 많은 사람들이 가입하는데 불편함이 없도록 하고 싶어 카카오 로그인을 채택하였다. 지극히 내 주위 사람들을 보고 선택한 결과여서 맞는 말이 아닐 수 있다. 그리고 개인적인 생각으로 네이버가 가지고 있는 서비스가 많아서 네이버 서비스끼리 연동할 수 있도록 간편 로그인을 권해서 많이 사용하는 느낌이 있다.

1.3 OAuth 구현 로직

🚧 카카오 파이어베이스 로그인 로직 아래 방법이 올바른 방법이 아니여서 수정했고 이 내용은 기록으로만 남겨둔 상태입니다.
🔥 수정한 트러블 슈팅 게시글 바로가기

앱에서 카카오 로그인을 통해 유저 정보를 받아와서 웹 서버에서 파이어베이스 로그인을 시킨다. 그와 동시에 커스텀 토큰을 발급받고 이 토큰으로 유효성을 검증한다.

내가 구현한 로직을 자세히 설명하면 아래 순서와 같다.

1.3.1. 웹뷰에서 카카오 로그인 버튼을 누르면 postMessage를 통해 리액트네이티브앱에게 이벤트를 전달한다.


이는 웹인 Next.JS에서 카카오 로그인 버튼을 누르면 실행되는 함수이다.

1.3.2. 리액트네이티브앱에서 카카오 SDK 로그인 하기 (간편 로그인)

리액트 네이티브에서 사용하는 웹뷰의 속성에서 onMessage가 있는데 여기서
1번에서 보낸 로그인 이벤트를 캐치해서 카카오 SDK 로그인 로직을 실행한다.

여기서 카카오 로그인을 위해 @react-native-seoul/kakao-login 라는 라이브러리를 사용하였다.

REST API가 아닌 Javascript SDK로 로그인를 사용하는 이유
하이브리드앱으로 '웹뷰'를 패키징한 형태로 서비스를 하게 될때는, REST API로 카카오 로그인을 하면 우리가 원하는대로 '카카오톡 앱' 이 호출되지 않고, 웹페이지에서 카카오 로그인 폼이 뜨게 된다.
또한 Rest Api 로그인을 요청해서 토큰을 발급받고 그 토큰으로 또 유저 정보를 서버에게 요청해야하는 다리가 하나 더 생긴다. 그렇기에 하이브리드앱에서 1초 간편 로그인을 위해서는 SDK 로그인을 사용해야 한다.

1.3.3. 로그인 하고 카카오에게 받은 유저 정보를 postMessage를 통해 웹뷰에게 전달한다.

1.3.4. 웹뷰에게 카카오 유저 정보를 전달했으니 리액트네이티브앱에서 카카오를 로그아웃 시킨다.

회원 정보와 관리는 모두 파이어베이스단에서 해결하기로 결정해서 바로 카카오를 로그아웃 시킨다.

1.3.5. 받은 카카오 유저 정보를 통해 Firebase Email로 로그인 한다. 만약 가입하지 않은 사용자면 회원가입 시킨다.


Next.JS의 API Route에서 로그인하고 커스텀 토큰을 발급받는 모습이다.

1.3.6. 4번과 동시에 파이어베이스 커스텀 토큰을 발급받고 이 토큰으로 유저 인증을 하면 된다.

이는 intercepteors를 이용해 모든 요청에 헤더에 토큰을 담는다.

1.3.7. 파이어베이스 서버에게 요청시 토큰이 만료되었다면 refreshToken으로 토큰을 재발급 받고 다시 서버에게 해당 API를 요청한다.


intercepteors를 이용해 모든 response에서 토큰이 유효하지 않다는 401에러가 발생하면

refreshAccessToken()를 실행시켜 토큰을 재발급 받고 이전에 요청한 API를 재요청한다.
이러면 사용자 입장에서 불편하지 않게 로그인이 안풀리는 경험을 할 수 있다

물론 리프레쉬 토큰도 만료되면 로그아웃이 되겠지만 파이어베이스의 리프레시 토큰은 아래의 경우 만료된다고 한다.
Firebase 공식 문서

  • 사용자가 삭제됨
  • 사용자가 비활성화됨
  • 사용자의 계정에서 중대한 변화가 감지됨. 비밀번호 또는 이메일 주소 업데이트 등의 이벤트가 여기에 해당합니다.
  • 보안상의 이유로 90일이 지나면 만료 (ChatGPT의 답변)

90일까지 리프레쉬 토큰이 유효해서 보안상 추후에 만료기간을 줄이는 것도 좋을 거 같다.

2. 앱이랑 웹끼리 어떻게 통신하지?

앱과 웹에서 서로 서버도 없는데 어떻게 통신할까?

2.1 ReactNative앱에서 NextJS웹에게 데이터 보내기

리액트네이티브의 웹뷰에서 자바스크립트 주입이 가능한데 통신을 위해서는 postMessage()를 사용하여 웹뷰와 웹 간에 안전하게 데이터를 교환할 수 있다고 한다. postMessage()는 Window라는 객체로 전송할 데이터를 보낸다.
그렇기에 자바스크립트 최상의 객체인 Window에서 앱에서 보낸 데이터를 수신할 수 있는 것이다.


useRef를 WebView에 연결해서 postMessage()로 전송한다.
postMessage는 Window(this) 오브젝트 사이에서 안전하게 cross-origin 통신을 가능하게 해준다고 한다.

2.2 Next.JS웹에서 ReactNative앱으로 데이터 보내기

리액트네이티브에게 window 객체를 통해 데이터를 전달 받았으니 보낼 수 도 있을 터, 리액트네이티브에게 window.ReactNativeWebView.postMessage()에 데이터를 담아서 보내면 된다.

나는 리액트네이티브에게 보내는 데이터가 페이지 이동(Route), 로그인 버튼, 이미지 크게보기에서 사용되는 이미지 수 (index) 등.. 많아서 따로 함수로 관리하고 있다.

2.4 ReactNative에서 NextJS웹이 보낸 데이터 받기

데이터 받는 동작원리는 리액트네이티브에서는 전송하는 것과 별 다를게 없다.


웹뷰의 onMessage 속성에서 해당 함수를 실행시킨 것이다. WebViewMessageEvent를 통해 데이터를 가져온다.

2.5 Next.JS웹에서 ReactNative앱이 보낸 데이터 받기

간단하게 보자면 리액트네이티브에게 받을 웹 페이지 코드 내에서 message 핸들러를 걸어 놓는다. 데이터를 받게 되면 message 핸들러의 이벤트 함수가 실행되고 원하는 로직을 작성하면 된다.

3. ReactNative 웹뷰를 적용하면서 발생한 이슈와 해결 방법

3.1 특정 상황에서 IOS 웹뷰가 흰 화면이 뜨는 문제

웹뷰를 로드하고 백그라운드에서 일정시간 있다가 다시 들어와 보면 웹뷰가 흰화면으로 나가있는 문제가 있었다.

스택오버플로우 글에서 WebView가 너무 많은 리소스를 사용하는 경우 iOS 에서 발생하며 WebView 충돌(빈 흰색 페이지 표시) 또는 앱 정지가 발생한다고 한다.
즉 다시 말하자면 react-native-webview 모듈에서 발생한 문제로 기기가 일정 부분 이상 메모리 리소스를 사용할경우 웹뷰가 강제 종료(terminated) 되는 현상이라고 보면 된다.

해결법으로는 Webview 속성에 onContentProcessDidTerminate props 콜백으로 웹뷰가 종료된 이후 액션을 정의 할 수 있다고 한다.

그렇게 강제 종료를 캐치해서 페이지를 새로고침 시켜서 흰 화면이 뜨는 문제를 해결했다.

3.2 웹뷰의 파일 선택 다이얼로그가 영어로 뜨는 문제


게시글 작성 페이지에서 웹의 input태그의 타입이 file을 열면 다이얼로그가 저렇게 뜨는데, 분명 나는 웹의 언어를 Ko로 잘 설정했는데 영어로 뜨는 것이었다.

리액트네이티브 웹뷰 레포 이슈에서 나와 같은 문제를 가진 사람이 이슈를 올렸었다. 거기서 uri를 올리는 source 라는 속성에 헤더에다가 웹뷰의 Language를 설정할 수 있다고 한다. 설정하지 않으면 기본으로 영어로 된다고 하니 웹 언어를 한글로 설정했음에도 리액트 네이티브 웹뷰 환경에서 영어로 파일 선택 다이얼로그가 뜬 이유가 설명이 된다.

위의 설명과 같이 헤더에다가 언어를 지정해주면 웹뷰에서 뜨는 다이얼로그가 한글로 바뀐다.

3.3 웹뷰에서 사진 찍어서 이미지 업로드하기 강제 종료

웹뷰여도 IOS 정책으로 사진 촬영에 접근하려면 사용자에게 권한을 물어야 한다.

권한 여부를 묻지 않고 위 사진에서 사진 찍기를 누르면 IOS기기의 카메라가 열리므로 접근 권한을 허용하는지 물어봐야 하는데, 처음에는 react-native-permissions 라이브러리를 사용해서 하려고 했는데 잘 적용되지 않았고 앱을 제출하려고 보니 경고가 떳는데 저 패키지를 설치하면서 딸려온 여러 권한들도 설정해줘야 하는 거 같았다. 그리고 AOS, IOS 설정도 따로 해줘야 해서 번거로움이 꽤 스트레스였다.

결국 그냥 info.plist에 권한을 요청하는 key를 추가해서 해결했다.

여기서 알게된 점은 사진 갤러리 접근은 권한 허용이 필요 없었다. 사람들이 작성한 이전 글들을 보면 네이티브앱이 아닌 웹앱(웹뷰)여도 사진 갤러리 접근 권한을 허용해야 한다는데, 나는 이 권한이 필요 없었다. 최근 정책에 바뀐 것일지도 모르겠다. 아무튼 스토어 심사도 제대로 통과했다.

3.4 AOS에서 카카오 간편 로그인이 안되는 이슈

안드로이드 시뮬레이터에서는 카카오 로그인이 잘 동작했는데, 실 기기에서는 동작하지 않는다고 들어서 급하게 아빠 폰을 빌려서 테스트 해봤다.
그런데 [TypeError: null is not an object (evaluating 'RNKakaoLogins.login')] 에러가 떠서 뭔가 했었다 이 에러는 라이브러리 참조가 제대로 안되어서 카카오 서버에 접근이 안되는거 같은데 분명 키해시도 잘 등록했는데 왜 그럴까?

여러 방법을 검색한 결과 안드로이드 키해시 얻는 법 이 분의 블로그를 찾았다.

구글 플레이 개발자 콘솔에서 Google play app signing 기능을 활성화했다면 구글 플레이에 앱이 릴리즈되기 전에 개발자의 로컬 개발 환경에서 릴리즈 키스토어의 시그너쳐가 삭제되고 구글 서버에 저장되어 있는 사이닝키의 시그너쳐로 교체되고 그렇기에 이 사이닝키로 생성한 키해시 또한 등록해줘야 한다고 한다.

그렇다 나는 분명 릴리즈키까지 적용했는데 안되던게 이 이유였다. 근데 나는 Google play app signing 기능을 활성화한 기억이 없는데 아마 기본값인가 싶다.

그렇게 구글 콘솔에서 SHA-1 인증서 지문을 복사해서 base64컨버트사이트에서 컨버트 하고 카카오디벨럽에 키해시를 하나 더 등록해주었다.

아니면 저 블로그분이 하신 방법대로 터미널에서 echo <구글플레이 SHA-1 인증서지문 입력> | xxd -r -p | openssl base64로 컨버트해도 된다.

signing 기능을 사용함으로써 얻는 이점
개발자가 APK 배포 시 앱 서명을 하던 기존 방식과 달리 구글 플레이 앱 서명은 업로드 키로 서명된 APK를 구글 플레이에 업로드하면 구글이 업로드 인증서를 확인 후 앱 서명 키로 재서명을 합니다. 업로드 서명은 이름 그대로 업로드 시 검증을 위해서만 사용되며 앱 서명 키로 서명되기 전 삭제됩니다. 이제 개발자는 로컬에 앱 서명 키가 아닌 업로드 키를 보관하면 되고, 앱 서명 키는 구글의 인프라를 통해 관리되므로 앱 서명 키를 분실하는 문제로부터 자유로워질 수 있습니다.

의도치 않게 보안에도 더 신경쓰게 되었다.

profile
고양이가 세상을 지배한다.

6개의 댓글

comment-user-thumbnail
2023년 4월 19일

개발자님의 노력이 느껴지는 글이네요! 아주 멋집니다💪🏻

답글 달기
comment-user-thumbnail
2023년 4월 19일

개발하는 과정들을 세밀하게 나열해주셔서 읽는 재미가 있었고 유익한 내용들도 많은 포스트임을 느꼈습니다. 지나가는 개발자 지망생인데 모티베이션 확실히 받고 갑니다 ~

답글 달기
comment-user-thumbnail
2023년 6월 19일

안녕하세요. 개발자가 아니라 작가신 줄 알았습니다. 글을 엄청 잘 쓰시네요.. 짝짝짝..
궁금한게 있는데 카카오 로그인을 할 때 신규 사용자인 경우 카카오에서 받은 유저 정보를 통해 Firebase Email 회원가입을 하게 되면 패스워드는 어떻게 지정하셨나요? Firebase 에서 email 로 회원가입 할 경우 패스워드도 넣게 되어 있던데..

1개의 답글
comment-user-thumbnail
2023년 8월 10일

혹시 웹뷰로 앱을 개발할때에 정말 https://abc.abc 같은 사이트만 띄우는 웹뷰앱이라면 앱 심사에서 거절되지 않나요? 혹시 웹뷰로 앱을 개발할때 정말 웹사이트만 띄우는 앱이 아닌 다른 것도 필요한가요?

답글 달기
comment-user-thumbnail
2024년 9월 20일

웹 - 앱 통신하면서 고생 많이 하셨을 것 같아요.
혹시 아예 rn으로 만드는방법은 별론가요 ?

답글 달기