Firebase Web 채팅앱 만들기 - 9. Realtime Database를 이용한 채팅기능 구현 - 채팅메세지 전송기능

1. 코드 작성

  • 채팅방화면에서 메세지입력 후 엔터키를 누르거나 삼각형 모양의 전송버튼을 누르면 메세지가 전송이 되는 기능을 구현
          /**
           * 초기 필드 변수 할당
           */
          FirebaseChat.prototype.init = function(){
              //...생략
              this.dvInputChat = document.getElementById('dvInputChat');
              this.iBtnSend = document.getElementById('iBtnSend');
              this.SPLIT_CHAR = '@[email protected]';
              this.ONE_VS_ONE = 'ONE_VS_ONE';
              this.MULTI = 'MULTI';
          }

          /**
           * 초기 이벤트 바인딩
           */
          FirebaseChat.prototype.initEvent = function(){
              //...생략
              this.dvInputChat.addEventListener('keydown', this.onEnterKey.bind(this));
              this.iBtnSend.addEventListener('click', this.saveMessages.bind(this));
          }

          /**
           * 메세지 엔터키 클릭
           */
          FirebaseChat.prototype.onEnterKey = function(e){
              if(e.keyCode === 13){ //엔터키 키코드가 입력이 되면
                  e.preventDefault();
                  this.saveMessages();
              }
          }

          /**
           * 메세지 전송
           */
          FirebaseChat.prototype.saveMessages = function(){
              var user = this.auth.currentUser;
              var msg = this.dvInputChat.innerHTML.trim();

              if(msg.length > 0){
                  this.dvInputChat.focus();
                  this.dvInputChat.innerHTML = '';
                  var multiUpdates = {};
                  var messageRefKey = this.messageRef.push().key; // 메세지 키값 구하기
                  var convertMsg = FirebaseChat.convertMsg(msg);


                  if(this.ulMessageList.getElementsByTagName('li').length === 0){ //메세지 처음 입력 하는 경우
                      var roomUserlistLength =this.roomUserlist.length;
                      for(var i=0; i < roomUserlistLength; i++){
                          multiUpdates['RoomUsers/' +this.roomId+'/' +this.roomUserlist[i]] = true;
                      }
                      this.database.ref().update(multiUpdates); // 권한 때문에 먼저 저장해야함
                      this.loadMessageList(this.roomId); //방에 메세지를 처음 입력하는 경우 권한때문에 다시 메세지를 로드 해주어야함
                  }

                  multiUpdates ={}; // 변수 초기화

                  //메세지  저장
                  multiUpdates['Messages/' +this.roomId + '/' + messageRefKey] = {
                      uid: user.uid,
                      userName: user.displayName,
                      message: convertMsg, // 태그 입력 방지
                      profileImg: user.photoURL ? user.photoURL : '',
                      timestamp: firebase.database.ServerValue.TIMESTAMP  //서버시간 등록하기
                  }

                  //유저별 룸리스트 저장
                  var roomUserListLength = this.roomUserlist.length;
                  if(this.roomUserlist && roomUserListLength > 0){
                      for(var i = 0; i < roomUserListLength ; i++){
                          multiUpdates['UserRooms/'+ this.roomUserlist[i] +'/'+ this.roomId] = {
                              roomId : this.roomId,
                              roomUserName : this.roomUserName.join(this.SPLIT_CHAR),
                              roomUserlist : this.roomUserlist.join(this.SPLIT_CHAR),
                              roomType : roomUserListLength > 2 ? this.MULTI : this.ONE_VS_ONE,
                              roomOneVSOneTarget : roomUserListLength == 2 && i == 0 ? this.roomUserlist[1] :  // 1대 1 대화이고 i 값이 0 이면
                                  roomUserListLength == 2 && i == 1 ? this.roomUserlist[0]   // 1대 1 대화 이고 i값이 1이면
                                      : '', // 나머지
                              lastMessage : convertMsg,
                              profileImg : user.photoURL ? user.photoURL : '',
                              timestamp: firebase.database.ServerValue.TIMESTAMP

                          };
                      }
                  }
                  this.database.ref().update(multiUpdates);
              }
          }

          /**
           *  메세지에 태그 입력시 변경하기
           */
          FirebaseChat.convertMsg = function(html){
              var tmp = document.createElement("DIV");
              tmp.innerHTML = html;
              return tmp.textContent || tmp.innerText || "";
          }

  • 코드 설명 :
    • 입력란에서 엔터키를 누르거나 전송버튼을 클릭했을 시 saveMessages 메소드가 실행이 되는 코드
    • multiUpdates 객체를 선언하고 저장할 위치와 값을 객체에 입력한 뒤 update 메소드로 저장.
    • update 메소드는 이렇게 여러 위치에 한번에 저장할 용도로 사용
    • push 메소드는 키값을 자동으로 생성하면서 데이터를 저장
    • aveMessages 메소드에서는 update메소드를 활용하여 데이터를 저장하므로 아래의 코드처럼 키를 생성해서 받을 수 있음
      • var messageRefKey = this.messageRef.push().key;
    • saveMessages 메소드는 메세지를 전송할때 3곳에 데이터를 저장(RoomUsers, UserRooms, Messages)
    • RoomUsers위치에 저장하는 데이터는 채팅방에서 메세지를 처음 보낼 때, 그리고 친구를 채팅방에 초대할 때에만 데이터를 저장.
    • 채팅방에 메세지가 있는지 체크한 후 저장하는 아래와 같은 코드가 작성되었습니다. 그리고 추후에 적용할 데이터베이스 권한부분 때문에 채팅방을 로드하는 메소드를 다시 한번 실행
if(this.ulMessageList.getElementsByTagName('li').length === 0){ //메세지 처음 입력 하는 경우
    var roomUserlistLength =this.roomUserlist.length;
    for(var i=0; i < roomUserlistLength; i++){
        multiUpdates['RoomUsers/' +this.roomId+'/' +this.roomUserlist[i]] = true;
    }
    this.database.ref().update(multiUpdates); // 권한 때문에 먼저 저장해야함
    this.loadMessageList(this.roomId); //방에 메세지를 처음 입력하는 경우 권한때문에 다시 메세지를 로드 해주어야함
}

- Messages에 데이터를 저장하기 위해 multiUpdates객체에 저장하는 코드
var convertMsg = FirebaseChat.convertMsg(msg); //메세지  저장
multiUpdates['Messages/' +this.roomId + '/' + messageRefKey] = {
    uid: user.uid,
    userName: user.displayName,
    message: convertMsg, // 태그 입력 방지
    profileImg: user.photoURL ? user.photoURL : '',
    timestamp: firebase.database.ServerValue.TIMESTAMP  //서버시간 등록하기
}
- 위치는 Messages아래에 방ID 값을 두고 그 아래에 메세지별 key 값을  조합하여 저장할 위치를 구성합니다.
- 데이터는 메세지 전송자의 uid 값, 메세지 전송자의 이름, 메세지, 프로필이미지, 등록되는 서버 시간 데이터가 저장이 됩니다. 
- 우선 Message데이터를 한번 convertMsg 메소드를 통해서 메세지에 포함된 태그를 제거하는 코드를 거치게 됩니다. 웹 채팅 특성상 태그의 입력을 막지 않으면 앱자체가 무력화가 될 수 있기 때문입니다.
- firebase.database.ServerValue.TIMESTAMP 는 서버시간으로 기록한 시간을 저장할 때 사용합니다. 
- firebase.database.ServerValue.TIMESTAMP 값은 한가지 유의 할점이 있습니다. 
- 데이터를 서버시간으로 저장하고나서, 다시한번 서버와 통신시간을 감안해서 보정된 값을 다시 한번 저장하게됩니다. 데이터의 저장이 두번 일어나게 됩니다.
      /**
       * 메세지 로드
       */
      FirebaseChat.prototype.loadMessageList = function(roomId){
                //...생략
                var cbDisplayMessages = function(data) {
                          //...생략
                          var val = data.val();
                          console.log('timestamp : ', val.timestamp);
                          //...생략
                 }
                          //...생략
                    this.messageRef.limitToLast(50).on('child_changed', cbDisplayMessages.bind(this));
                }
      }
- console.log 로 timestamp 값을 받을 것이며, 메세지 수신 이벤트에는 child_added 이벤트에다가 child_changed  이벤트를 추가하였습니다. 메세지를 입력해보시면 메세지는 두개가 입력이 되고 콘솔에 다음과 같이 찍히게됩니다.

8-1

- timestamp 값을 입력하시는 경우 이 점에 유의하셔야 합니다. 이 부분은 버그가 아니라 의도된 사항
  • 채팅창 입력 부분은 완성되었습니다. 하지만 한가지 보완해야할 부분이 있습니다.
  • 메세지를 입력하는 곳의 소스를 살펴보면, input 태그로 되어 있지 않고, div 태그에 contenteditalbe=’true’ 속성으로 되어 있습니다.
  • 레이아웃 조정을 손쉽게 하기 위해 적용하였습니다만, 한가지 문제가 있습니다. 다른 웹에 있는 내용을 복사해서 붙여넣기를 하면 태그까지 따라오게 됩니다.
<div id="dvInputChat" contenteditable="true" placeholder="메세지 작성"></div>
  • 복사 후 붙여넣기 시에 일반 텍스트로 치환하기 위해 아래의 코드를 입력
          /**
           * 초기 이벤트 바인딩
           */
          FirebaseChat.prototype.initEvent = function(){
              //...생략
              this.dvInputChat.addEventListener("paste", this.onPaste.bind(this));
          }

          /**
           * 복사 후 붙여넣기 시에 실행
           */
          FirebaseChat.prototype.onPaste = function(e){
              e.preventDefault();
              var text = e.clipboardData.getData("text/plain");
              document.execCommand("insertText", false, text);
          }