VueJS 채팅 어플리케이션 시리즈2는 시리즈 1에 이어서
1.sendMessage - 일반 텍스트 메세지
2.sendFileMessage - Image, Video, File등의 메세지를 chunking 하여 보내보는 시리즈입니다.
메세지를 전송할때 카카오톡같은 메신저는 로컬에 메세지를 저장한후 메세지전송이 안되면 재전송을 할 수 있는 기능을 지원합니다.
일반텍스트 메세지 전송은 간단합니다.
syncKey: 유니크한키(서버에서 리턴되는 synckey와 매칭하여 현재메세지가 잘전송이 되었는지 확인하는 용도)
roomId: 메세지를 전송할 채팅방 id
content: 메세지 내용
sendMessage() {
if (!this.message) return;
const payload = {
syncKey: new Date().getTime().toString(),
roomId: this.activeRoom._id,
content: this.message,
};
//새로운 메세지 추가
this.messages.push({
_id: null,
syncKey: payload.syncKey,
roomId: this.activeRoom._id,
authorId: this.me._id,
data: {
type: 'TEXT',
content: this.message,
},
});
this.$socket.emit('sendMessage', payload, (data) => {
console.log(data);
});
this.message = null;
},
메세지를 받는 부분은 시리즈 1에 이미 나온 내용이지만 한번 더 짚고 가겠습니다.
메세지를 받는 방법은 joinRoom을 할때 현재 채팅방 id를 가지고 새로운 이벤트를 subscribe(구독) 하는 것입니다.
this.sockets.subscribe(`listenMessage_${room._id}`, (message) => {
if (message) {
const findIdx = this.messages.findIndex((m) => m.syncKey == message.syncKey);
if (findIdx > -1) {
//위에서 추가한 message가 있을경우에는 단순히 업데이트만!
this.messages[findIdx] = message;
} else {
this.messages.push(message);
}
this.scrollToBottom();
}
});
반대로 방에서 나올때 구독을 해지하면 위의 이벤트 트리거를 해지할 수 있습니다.
this.sockets.unsubscribe(`listenMessage_${room._id}`);
웹소켓의 바이트 최대전송사이는 1mb입니다. 그래서 이 예제에서는 100kb 단위로 청킹하여 업로드하기로 합니다. 500kb로 청킹해도 괜찮습니다.
chunkFile(e){
var file = e.target.files[0];
if (file) {
this.tempFile = file;
this.messageKey = md5(file.name + new Date());
const messagePayload = {
_id: null,
key: this.messageKey,
roomId: this.activeRoom._id,
authorId: this.me._id,
progress: 0,
data: {
type: 'FILE',
content: null,
},
};
this.messages.push(messagePayload);
this.scrollToBottom();
let slice = this.tempFile.slice(0, 100000, file.type);
this.sendFileMessage(slice);
}
}
파일을 전송중인 화면입니다.
uploadingg...
49.xxxx 퍼센테이지를 표시하는 화면입니다.
sendFileMessage(slice) {
this.fileReader.readAsArrayBuffer(slice);
this.fileReader.onload = () => {
var arrayBuffer = this.fileReader.result;
var type = 'FILE';
if (this.tempFile.type.startsWith('image/')) {
type = 'PHOTO';
} else if (this.tempFile.type.startsWith('video/')) {
type = 'VIDEO';
}
this.$socket.emit(
'sendFileMessage',
{
syncKey: this.messageKey,
data: arrayBuffer,
type: type,
size: this.tempFile.size,
name: this.tempFile.name,
roomId: this.activeRoomId,
},
(data) => {
console.log('currentSlice', result);
const success = result.success;
const data = result.data;
if (success && !data.done && data.slice) {
//파일업로드가 완료되면 다음 슬라이스를 청킹하여 업로드!
var offset = data.slice * this.splicedSize;
var slice = this.tempFile.slice(
offset,
offset +
Math.min(this.splicedSize, this.tempFile.size - offset),
this.tempFile.type,
);
this.sendFileMessage(slice);
}else if(data.done) {
//파일업로드 완료시, progress 상태 구독해재
this.sockets.unsubscribe(`progress_${this.messageKey}`);
} else if(!success) {
//파일업로드 실패시, progress 상태 구독해재
this.sockets.unsubscribe(`progress_${this.messageKey}`);
},
);
//file upload의 progress 상태 구독하기
this.sockets.subscribe(`progress_${this.messageKey}`, (progress) => {
var idx = this.messages.findIndex((m) => m.syncKey == this.messageKey);
if (idx > -1) {
this.messages[idx].progress = progress;
}
});
};
},
파일 메세지 전송이 완료된후의 화면입니다.