- 채팅창에서 아래와 같은 파일 전송기능을 구현 합니다.
1. Firebase Storage 설정 작업
- Firebase console 로 들어가서 Storage 메뉴로 들어가 시작하기 버튼을 누릅니다.
- 시작하기 버튼을 눌러야 기본적인 권한이 생성이 되고 Bucket이 생성이 되며 Storage를 사용할 수 있는 상태가 됩니다.
2. 코드 작성
/**
* 초기 필드 변수 할당
*/
FirebaseChat.prototype.init = function(){
//...생략
this.iBtnAttach = document.getElementById('iBtnAttach');
this.attachFile = document.getElementById('attachFile');
}
/**
* 초기 이벤트 바인딩
*/
FirebaseChat.prototype.initEvent = function(){
//...생략
this.iBtnAttach.addEventListener('click', this.onBtnAttachClick.bind(this));
this.attachFile.addEventListener('change', this.onAttachFile.bind(this));
}
/**
* 첨부파일 버튼 클릭
*/
FirebaseChat.prototype.onBtnAttachClick = function(){
this.attachFile.click();
}
/**
* 첨부파일 전송
*/
FirebaseChat.prototype.onAttachFile = function(event){
$('#dnModal').modal('open');
var files = event.target.files;
var filesLength = files.length;
var progressBar = document.getElementById('dvProgressBar');
var fileName = files[0].name;
var path = FirebaseChat.yyyyMMddHHmmsss().substr(0, 8) +'/'+this.roomId + '/' + this.auth.currentUser.uid + '/' + fileName;
var uploadTask = firebase.storage().ref().child(path).put(files[0]);
var cbProgress = function(snapshot){ // 진행 과정
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
progressBar.style.width = progress+'%';
}
var cbError = function(error) { // 에러발생
console.log(error);
$('#dnModal').modal('close');
alert('업로드 중 에러가 발생하였습니다.');
}
var cbComplete = function() { // 완료
//프로그레스바 닫기
$('#dnModal').modal('close');
//완료 다운로드 링크 메세지 보내기
this.saveMessages(null, uploadTask.snapshot.downloadURL, fileName);
//files 리셋
event.target.value='';
}
//프로그레스바
uploadTask.on('state_changed', cbProgress.bind(this) , cbError.bind(this), cbComplete.bind(this));
}
/**
* 메세지 전송
*/
FirebaseChat.prototype.saveMessages = function(inviteMessage, downloadURL, fileName){
var user = this.auth.currentUser;
var msg = this.dvInputChat.innerHTML.trim();
//초대메세지
if(inviteMessage && inviteMessage.length > 0){
msg = inviteMessage;
}
//파일전송 메세지
if(downloadURL && fileName){
msg = "<a class='waves-effect waves-light btn blue' download='"+fileName+"' href='"+ downloadURL +"'>다운로드</a></br><span class=''>파일명 : "+ fileName +"</span>";
}
if(msg.length > 0){
this.dvInputChat.focus();
this.dvInputChat.innerHTML = '';
var multiUpdates = {};
var messageRefKey = this.messageRef.push().key; // 메세지 키값 구하기
var convertMsg = downloadURL ? msg : FirebaseChat.convertMsg(msg); //다운로드 URL일 경우 convert하지 않음
//...생략
//유저별 룸리스트 저장
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 : downloadURL ? '다운로드' : convertMsg,
profileImg : user.photoURL ? user.photoURL : '',
timestamp: firebase.database.ServerValue.TIMESTAMP
};
}
}
this.database.ref().update(multiUpdates);
}
}
- 코드 설명 :
- 채팅방 화면에서 클립모양의 아이콘을 클릭 하면 file 타입의 input 태그를 클릭하게 되고, 파일이 선택되어지면 onAttachFile 메소드가 실행.
- onAttachFile 메소드 :
- 메소드의 시작코드는 파일 다운로드 팝업을 띄움
- 선택한 파일은 event.target.files 에 데이터가 들어옴
- 파일을 하나 선택할 뿐이지만 형태는 fileList 객체로 데이터가 전달됨.
- 배열처럼 첫번째 인덱스의 파일을 선택하여 파일명 변수에 할당
var files = event.target.files; var fileName = files[0].name;
- 파일을 저장하는 위치는 ‘/날짜(yyyyMMdd)/방ID/유저UID/파일명’
- 파일 저장 경로 중에 날짜를 둔것은 Firebase Functions 기능을 통해서 주기적으로 삭제를 실시하기 위함
var path = FirebaseChat.yyyyMMddHHmmsss().substr(0, 8) + '/'+this.roomId + '/' + this.auth.currentUser.uid + '/' + fileName;
- 실질적으로 파일을 저장하는 코드 부분
- ref메소드는 Realtime Database와 비슷하게 저장되는 경로를 의미 합니다.
- 실질적인 저장은 Google Cloud Storage에 저장
- put 메소드는 UploadTask 객체를 반환 합니다.
- UploadTask 객체로 업로드 관리를 수행
var uploadTask = firebase.storage().ref().child(path).put(files[0]);
- UploadTask객체는 ‘state_changed’ 이벤트를 발생.
- 아래의 코드는 ‘state_changed’ 이벤트를 받았을 때 3가지의 콜백 함수를 파라미터로 지정한 코드.
- 첫번째 콜백함수는 진행 중 수행하는 함수이고, 두번째는 에러가 발생했을때, 세번째는 업로드가 완료됬을 때 수행됩니다
uploadTask.on('state_changed', cbProgress.bind(this) , cbError.bind(this), cbComplete.bind(this));
- 진행 중 수행하는 콜백 함수인 cbProgresss를 살펴보면, 프로그래스바의 게이지의 넓이를 조정
var cbProgress = function(snapshot){ // 진행 과정 var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; progressBar.style.width = progress+'%'; }
- 에러 콜백함수인 cbError을 살펴보면, 사용자에게 에러가 발생하였음을 alert창으로 알리고, 콘솔에 에러내용을 출력
var cbError = function(error) { // 에러발생 console.log(error); $('#dnModal').modal('close'); alert('업로드 중 에러가 발생하였습니다.'); }
- 마지막으로 완료 콜백 함수인 cbComplete 함수를 보면, 팝업 모달창을 닫고, 다운로드 url정보가 담긴 메세지를 채팅방에 메세지를 전송
var cbComplete = function() { // 완료 //프로그레스바 닫기 $('#dnModal').modal('close'); //완료 다운로드 링크 메세지 보내기 this.saveMessages(null, uploadTask.snapshot.downloadURL, fileName); //files 리셋 event.target.value=''; }
3. Storage 권한 부여
- Firebase Storage는 무료의 경우 저장용량 5GB,다운로드 크기 하루 1GB, 하루 업로드 20000회, 다운로드 50000회의 제한이 있습니다.
- 유료 종량제 요금의 경우 이러한 제한은 없지만, 사용한 만큼 비용이 청구
- 이러한 이유로 Storage는 보안 설정이 중요합니다.
-
보안 설정을 통해서 접근 권한이나 업로드 용량, 업로드 가능한 파일 타입을 지정할 수 있습니다.
- 아래 코드는 초기 기본 권한
- Authentication을 통해서 인증 받은 사람들에게 읽기와 쓰기 권한이 부여
- 프로젝트에 storage.rules 파일에서 확인 가능
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}
- 수정할 권한 내용은 아래와 같습니다.
- 읽기권한은 인증받은 유저 모두에게 권한을 부여하나 쓰기 권한은 본인의 유저UID 경로에만 접근해서 저장
- 이미지 파일과 zip확장자를 가진 파일만 업로드가 되도록 하고, 용량은 5메가바이트 미만
service firebase.storage {
match /b/{bucket}/o {
match /{yyyyMMdd}/{roomId}/{userUid}/{file} {
allow read: if request.auth!=null;
allow write: if request.auth!=null
&& request.auth.uid == userUid
&& (request.resource.contentType.matches('image/.*') || file.matches(".*.zip"))
&& request.resource.size < 5 * 1024 * 1024;
}
}
}