개요

이전 포스팅에선,
서로 다른 웹 브라우저 환경에서 실시간으로 메세지를 보내고 받는 기능을 구현했다.

이번 포스팅에서는 상대방 혹은 자신이 보낸 메세지들을
채팅 기능과 유사하게 메세지를 보낸 사람이 누구인지와
받은 메세지들을 리스트형태로 화면에 출력하는 로직을 구현해 보고자 한다.

a : text
b : text
a : text1
...

UI

이전 포스팅에서 home.pugul 태그를 생성해 두었다.
그 아래에 메세지를 받을 경우 리스트형태로 쌓일 수 있게 li태그를 생성한다.
li태그는 front.js 영역의 MessageEvent 영역에 다음과 같이 추가 해 준다.

const li = document.createElement('li') // 새로운 메세지를 받으면 요소 생성
li.innerText = message.data // message.data 를 li 안에 담음
MessageList.append(li) // li 를 MessageList 안에 추가해줌

liMessageList에 append 해 줌으로써 메세지를 Front 영역의 화면에서 볼 수 있다.

다음과 같이 익명의 상대가 보낸 메세지를 리스트 형태로 출력 해 준다.

userName 지정

home.pug에서 form태그를 하나 더 생성 해 준다.
placeholder에는 아이디를 입력하고 저장할 수 있는 버튼을 생성 해 준다.

생성한 아이디를 Server 로 보내야 하는데,
고려해야할 사항으로는,

  1. Server 는 Message 들을 구분하지 못한다.
    • 메세지를 보낼 때 입력받은 그대로를 출력하기 때문.
  2. 메세지를 구별 해 주어야 함.
    • 누가 어떤 메세지를 보냈는지 ?
    • 아이디를 저장하기 위한 메세지 일 수도 있으며, 텍스트로 구성된 메세지 일 수도 있음.

Server.js의 Message 함수는 모든 메세지를 포함하고 있기 때문

먼저,
home.pugform태그에 Id 를 부여 해 준다.

    form#id
        input(type="text", placeholder="아이디를 입력하세요", required)
        button save 
    ul
    form#message
        input(type="text", placeholder="텍스트를 입력하세요", required)
        button send 

Client.js에서 정의한 MessageForm 을 Id 가 부여된 #message 로 변경 해 준다.
그리고 Id 와 Message 를 구분하기 위해 IdForm 을 추가로 생성 해 준다.

const MessageList = document.querySelector('ul')
const MessageForm = document.querySelector('#message')
const idForm = document.querySelector('#id')

생성한 idForm 에 addEventListener를 넣어준다.
(함수 handleIdSubmit 생성)

function handleIdSubmit(event){
    event.preventDefault()
    const input = idForm.querySelector('input')
    socket.send(input.value) // send 메소드는 string 타입만을 받을 수 있음
    input.value = ""
}

idForm.addEventListener('submit', handleIdSubmit)

JSON

위의 로직대로 실행 해 보면,
아이디를 입력하고 버튼을 누르게 되면 메세지 형태로 리스트에 추가가 된다.

우리가 원하는 것은 아이디는 따로 서버에 저장이 되며,
저장된 아이디가 보내는 메세지만 리스트에 담기는 형태일 것이다.

때문에,
JSON 의 형태를 가진 2개의 Type 으로 id 와 메세지를 구분지을 수 있었으면 좋겠다.

{
    type: "id",
    payload: "a"
},

{
    type: "message",
    payload: "hello"
}

Client.js에 함수를 생성 해 준다.

// 메세지 전송을 위한 함수 생성
function makeMessage(type, payload){
    const msg = { type, payload }
    return jSON.stringify(msg) // JavaScript Object 를 String 으로 변환해주는 메소드
}

// 적용
function handleSubmit(event){
    event.preventDefault()
    const input = MessageForm.querySelector('input')
    socket.send(makeMessage('new message', input.value))
    input.value = ""
}
// 적용
function handleIdSubmit(event){
    event.preventDefault()
    const input = idForm.querySelector('input')
    socket.send(makeMessage('id', input.value))
}

makeMessage함수를 생성해
기존에 존재했던 handleSubmithandleIdSubmit 에 적용하면,
서버에 메세지를 전송 할 때마다 string type 을 전송 해 줄 것이다.

다음과 같이 객체 형태로 출력이 되었는데,
이는 Server 가 JavaScript Object 를 읽을 수 없어 String 형태로 출력이 되고있다.

다른 언어로 해당 로직을 구현하려면 새로 작성을 해야하지만,
String 타입으로 보내주었기 때문에 Server 에서는 다양한 언어로 로직을 구현할 수 있다.

우리는 JavaScript 로 구현할 것이기 때문에,
Object 형태로 받은 String 을
JavaScript Object 의 형태로 변환 해주어야 Type 을 눈으로 확인 할 수 있다.


.parse

앞서 JavaScript Object 를 String 으로 변환해주는 메소드로 .stringify를 사용했었다면,
반대로 String 을 JavaScript Object 로 변환 해 주는 작업이 필요할 것이다.

우리는 .parse 메소드를 사용하여 JSON 형태의 String 을 JavaScript Object 로 변환 해 줄 것이다.

Server.js의 메세지 영역에 아래 코드를 추가하고 메세지를 전송 해 보자.

const parsed = JSON.parse(message)
console.log(parsed, message.toString('utf8'))

첫 번째로 출력된 것이 JavaScript Object 이고,
두 번째로 출력된 것이 String 임을 확인할 수 있다.

우리가 사용할 것은 JavaScript Object 이기 때문에 parsed 를 잘 기억 해 두자.


테스트

로직을 작성하여 메세지를 보내 작동이 잘 되는지 테스트 해 보도록 하자.

// Server.js
socket.on("message", (message) => {
    const parsed = JSON.parse(message)
    if (parsed.type === "new_message") {
        otherBrowser.forEach((aSocket) => aSocket.send(parsed.payload))
    } else if (parsed.type === "id") {
        console.log(parsed.payload)
    }
})

예상했던 대로,
Id 를 입력했을 때는 메세지가 리스트에 출력되지 않고,
메세지를 입력했을때만 리스트에 출력됨을 확인할 수 있었다.

else if 문이 아닌 switch 문법을 사용하여 코드를 리펙토링 함.

// Server.js
socket.on("message", (message) => {
    const parsed = JSON.parse(message)
    switch (parsed.type) { // 만약 parsed.type 이 
        case "new_message": // new_message 와 같다면 아래 코드 실행
            otherBrowser.forEach((aSocket) => aSocket.send(parsed.payload))
        case "id":
            console.log(parsed.payload)
    }
})

parsed 를 message object 형태로 변환해줄 필요가 있다.

// Server.js
socket.on("message", (mag) => { // msg 라는 string 을 받고
    const message = JSON.parse(mag) // parse 하여 message 가 됨
    switch (message.type) { // 만약 parsed.type 이 
        case "new_message": // new_message 와 같다면 아래 코드 실행
            otherBrowser.forEach((aSocket) => aSocket.send(message.payload))
        case "id": // id type 의 message 를 받으면 
            socket['id'] = message.payload // id 를 socket 에 넣어줌
    }
})

예외처리

모든 사람이 id 를 저장하고 메세지를 보내지 않을 가능성을 생각하여,
id 를 저장하지 않은 사람이 메세지를 보낼 경우인
익명 메세지에 대한 예외처리를 진행한다.

// Server.js
socket["id"] = "NaN"


출력(최종)

메세지의 형태는 단순한 payload 형태만 받는 것이 아닌,
id property 를 socket object 에 저장하고,
payload property 를 message JSON 에 저장한다.

`` (==백틱, 템플릿 리터럴) 을 사용하여 변화하는 문자열을 삽입한다.

aSocket.send(`${socket.id} : ${message.payload}`)

최종 코드

회고 및 개선사항

다음과 같이 메세지를 두번 이상 보내는 경우 저장된 아이디가 없어지고,
직전에 전송한 메세지가 아이디로 저장되어
메세지 : 메세지 형태로 출력되고있다.

아이디는 아이디대로 서버에 저장되어 유지되어야 하며,
유지된 아이디에서 보낸 텍스트만 리스트에 저장되는 형태로 코드를 개선해봐야겠다.

Reference

https://nomadcoders.co/noom/lectures/3096
https://eblee-repo.tistory.com/38