Firebase Web 채팅앱 만들기 - 6. Realtime Database를 이용한 채팅기능 구현 - 유저데이터 저장하기

1. 유저데이터 저장 시 고민 사항

  1. 유저데이터를 저장하는 시점 :
    • Realtime Database의 특성상 권한이 부여된 후 저장이 되는 것이 바람직
    • 가입 메소드에서 수행하면 아직 미인증 상태이므로 기본을 설정된 데이터베이스 권한으로는 저장할 수 없음
    • 저장 위치는 onAuthChange 메소드에서 저장
  2. onAuthChange 저장 메소드 위치 시 로그인할 때마다 프로세스 실행
    • 새로 가입한 유저데이터를 저장하는 프로세스를 로그인할 때마다 저장하는 것은 Realtime Database 특성상, 만약에 Users 위치를 클라이언트에서 변화 감지해서 데이터를 받아오는 코드가 작성되어 있다면 불필요한 데이터 로딩을 일으킬 수 있음.
    • Realtime Database에 Users데이터가 저장되어 있는지 확인 후 저장을 수행하거나 로컬(localStorage, indexedDB 등등)에 해당 유저의 데이터를 데이터베이스에 기록했는지 여부를 저장하여 확인 후 저장을 수행.
    • Users데이터가 저장되어 있는지 매번 확인하는 것은 불필요한 데이터베이스 접근이므로 두가지 방법을 함께 사용하여 코드를 작성.

2. 코드 작성

  • offline storage중 하나인 IndexedDB에 가입 시 데이터를 저장하는 코드 추가
    • IndexedDB에 대한 내용은 아래 링크 참조
    • 코드 설명:
      • init메소드에는 saveUserAtIndexedDB메소드에서 사용할 상수들이 정의
      • saveUserAtIndexedDB는 가입 시 수행되는 createEmailUser메소드와 signInWithPopup메소드에서 실행 중에 받게되는 user object를 파라미터로 받게되고, userName파라미터는 이메일 가입시에는 유저 이름 파라미터를 user object안에서는 받을 수 없으므로 따로 받도록 파라미터를 추가
      • isSave파라미터는 후에 Realtime Database 에 유저 데이터를 저장 한 후에 값을 변경 저장하기 위한 구분 값.
      • saveUserAtIndexedDB는 createEmailUser메소드와 signInWithPopup 메소드 안에 추가
         /**
           * 초기 필드 변수 할당
           */
          FirebaseChat.prototype.init = function(){
              //...생략
              this.INDEXDB_DB_NAME = "USER";
              this.INDEXDB_VERSION = 1;
              this.INDEXDB_STORE ="Users";
          }


          /**
           * User데이터를 IndexedDB에 저장 및 데이터 변경
           */
          FirebaseChat.prototype.saveUserAtIndexedDB = function(user, userName, isSave){
              if(indexedDB){
                  var request = indexedDB.open(this.INDEXDB_DB_NAME , this.INDEXDB_VERSION); // 데이터베이스 접근 요청
                  var objectName = this.INDEXDB_STORE
                  request.onupgradeneeded = function(){  // 데이터 베이스 생성 또는 버전 업그레이드 시 수행
                      var db = request.result; //데이터 베이스 객체
                      var store  = db.createObjectStore(objectName, {keyPath :"uid"}); // 테이블에 해당하는 Object 생성 및 키값 설정
                  }
                  request.onsuccess = function() {  // 데이터베이스 접근 요청이 성공 시
                      var db = request.result;
                      var tx = db.transaction(objectName, "readwrite"); //읽기쓰기 권한으로 트랜잭션을 얻음
                      var store = tx.objectStore(objectName);

                      store.get(user.uid).onsuccess = function(event){  //user.uid 기준으로 IndexedDB 데이터를 읽어온다
                          var data = event.target.result;
                          console.log('IndexedDB query 결과 : ', data);
                          console.log('saveUserAtIndexedDB isSave 파라미터', isSave);
                          if(!data){  //데이터가 없으면 저장
                              store.put({ 
                                  uid: user.uid
                                  , email: user.email
                                  , photoURL : user.photoURL ? user.photoURL : ''
                                  , displayName : userName ? userName : user.displayName
                                  , isSave : false
                              });
                          }

                          if(data && isSave){  // 데이터가 존재하고 isSave 파라미터 true이면 데이터를 업데이트
                              store.put({  
                                  uid: user.uid
                                  , email: user.email
                                  , photoURL : user.photoURL ? user.photoURL : ''
                                  , displayName : userName ? userName : user.displayName
                                  , isSave : true
                              });
                          }
                      }
                      tx.oncomplete = function() {
                          console.log('IndexedDB 트랜잭션 완료')
                          db.close();
                      };
                  }
              }
          }

          /**
           * 이메일로 가입 처리
           */
          FirebaseChat.prototype.createEmailUser = function(){
                  //...생략 
                  var cbCreateUserWithEmail = function(user){
                      this.saveUserAtIndexedDB(user ,userName, false); //추가 메소드
                      //...생략 
                  }
                  //...생략
          }

          /**
           * 지속성 설정 후 sign-in 팝업창
           */
          FirebaseChat.prototype.signInWithPopup = function(provider) {
              var cbSignIn = function(result){
                  console.log('로그인 성공')
                  this.saveUserAtIndexedDB(result.user, null , false);
              }
              //...생략 
          }
  • onAuthChange메소드 안 setLogin메소드안에서 IndexedDB의 데이터의 데이터로 Users 데이터베이스에 저장하였는지 여부(isSave) 를 확인 한 후 저장하지 않았으면 Realtime Database에 유저데이터를 저장하고 다시 IndexedDB에 값을 저장하였다는 것을 표시하는 코드 추가
    • 코드 설명:
      • 가입 후 onAuthChange메소드 수행 이후 동작
      • IndexedDB의 데이터의 데이터로 Users 데이터베이스에 저장하였는지 여부(isSave) 를 확인하는 코드인 checkAndSaveUser메소드는 saveUserAtIndexedDB메소드와 비슷하게 진행
      • IndexedDB를 오픈하고, IndexedDB안에 User Object를 User의 uid 값을 기준으로 검색하여 isSave값이 false인 경우에만 saveUserAtRealDB 메소드를 실행하여 Realtime Database에 유저 데이터를 저장
      • saveUserAtRealDB 메소드에서 사용된 Realtime Database 의 Reference객체의 메소드 once는 데이터를 한번 수신하고, 더 이상 변경사항에 대해서는 데이터를 수신하지 않는 메소드이며, cbUserRefResult 콜백 함수가 수행. cbUserRefResult 콜백함수에서는 dataSnapShot 객체를 얻을 수 있고 자식객체가 있는지를 체크한 후 없다면 set 메소드로 저장을 수행
          /**
           * 인증 정보가 변화 되었을 시에 변화
           */
          FirebaseChat.prototype.onAuthChange = function(user){
              if (user) {
                  console.log('user로그인 : ',JSON.stringify(user));
                  this.setLogin(user); //파라미터 추가
              } else {
                  console.log('로그아웃');
                  this.setLogOut();
              }
          }

          /**
           *  로그인 후 세팅
           */
          FirebaseChat.prototype.setLogin = function(user){  //user 파라미터 추
              //...생략
              this.checkAndSaveUser(user);
          }

          /**
           * 신규 User를 IndexedDB에서 체크 후 저장
           */
          FirebaseChat.prototype.checkAndSaveUser = function(user){
              try{
                  var request = indexedDB.open(this.INDEXDB_DB_NAME , this.INDEXDB_VERSION);
                  var objectName = this.INDEXDB_STORE;
                  request.onsuccess = function() {
                      var db = request.result;
                      var tx = db.transaction(objectName, "readwrite");
                      var store = tx.objectStore(objectName);

                      store.get(user.uid).onsuccess = function(event){ // indexedDB에서 uid값으로 쿼리
                          var data = event.target.result;
                          console.log('IndexedDB query 결과 : ', data);
                          console.log('checkAndSaveUser isSave' , data.isSave);
                          if(!data.isSave){
                              fbChat.saveUserAtRealDB(data);
                          }
                      }
                      tx.oncomplete = function() {
                          console.log('IndexedDB 트랜잭션 완료')
                          db.close();
                      };
                  }
              }catch(e){
                  this.saveUserAtRealDB(user); //indexDB 확인에 실패 하면 Reatime Database 조회 후 
              }
          }

          /**
           *  Realtime Database에서 Users 데이터를 체크 후 저장
           */
          FirebaseChat.prototype.saveUserAtRealDB = function(user){
              var userRef = this.database.ref('Users/' + user.uid);
              var cbUserRefResult = function(dataSnapShot){ // User Ref에 데이터가 없을 경우 데이터 저장
                  if(!dataSnapShot.hasChildren()){
                      console.log('saveUserAtRealDB 저장');
                      userRef.set({  //데이터 저장
                          email: user.email
                          , profileImg: user.photoURL ? user.photoURL : ''
                          , userName : user.displayName
                      }).then(cbUserAfterSave.bind(this));
                  }
              }
              var cbUserAfterSave = function(){ //Realtime Database에 저장이 완료된 후 로컬 IndexedDB isSave 값을 true로 변경
                  this.saveUserAtIndexedDB(user, null, true);
              }

              userRef.once('value')  // 1회만 검색하고 변경데이터를 수신하지 않음
                  .then(cbUserRefResult.bind(this));
          }

3. 테스트

  • 점검을 할 때는 Firebase console화면에서 테스트에 사용할 아이디는 초기화 해주세요.
  • 가입을 다시 수행하고 개발자 도구를 열어 크롬의 경우 Application 탭으로 들어가 봅니다.
  • Firefox는 개발자 도구에 저장소 라는 탭이 있습니다.
  • 좌측 IndexedDB 라는 항목을 보시면 아래와 같이 저장이 되어 있는 것을 확인할 수 있습니다.
  • isSave 항목이 정상적으로 모두 true로 변경되어 있음을 확인 할수 있습니다.

6-1

  • Firebase Realtime Database에서 확인

6-2

챕터완성소스 : Realtime Database를 이용한 채팅기능 구현 - 유저데이터 저장하기 소스