-
socket.io 이용한 채팅Programming/NodeJS 2020. 4. 2. 15:48반응형
socket.io를 이용한 채팅 기능을 만들면서 복습겸 정리, 아 이런거구나 감을 잡는데 도움이 되었으면 좋겠습니다.
좌 - 닉네임 입력전 , 우 - 닉네임 입력 하면 채팅 view 보이게 누군가 입장시 알림
// home.pug <!DOCTYPE html> html(lang="ko") head meta(charset="UTF-8") meta(name="viewport", content="width=device-width, initial-scale=1.0") link(rel="stylesheet", href="styles/styles.css") title Chat body #jsNotifications // 유저입장시 입장알림 .loginBox // 닉네임 입력 안했을 때 보이는 창 form#jsLogin input(placeholder="닉네임!", type="text") .chatContainer#jsChat // 닉네임 입력 했을때 보이는 채팅창 .chat ul.chat__messages#jsMessages form.chat__form#jsSendMessage input(placeholder="메시지 입력" type="text") script. window.events = !{events} script(src="/socket.io/socket.io.js") script(src="/js/main.js") // styles.scss body { background-color: $greyColor; font-family: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100%; .loginBox, .chatContainer { display: none; } &.loggedOut { .loginBox { display: block; } } &.loggedIn { .chatContainer { display: block; } } }
server.js
import { join } from 'path'; import express from 'express'; import socketIO from 'socket.io'; import morgan from 'morgan'; import socketController from './socketController'; import events from './events'; const PORT = 4000; const app = express(); app.set('view engine', 'pug'); app.use(morgan('dev')); app.set('views', join(__dirname, 'views')); app.use(express.static(join(__dirname, 'static'))); app.get('/', (req, res) => res.render('home', { events: JSON.stringify(events) })); const handleListening = () => console.log(`✅ Server running: http://localhost:${PORT}`); const server = app.listen(PORT, handleListening); // socket io const io = socketIO.listen(server); io.on('connection', socket => socketController(socket));
home.pug : 기본적으로 styles.scss에서 .loginBox와 chatContainer에 기본 display:none을 주고, body class 로 로그인창을 숨기고 채팅창을 보이게하고 함
.loginBox, .gameContainer { display: none; } &.loggedOut { .loginBox { display: block; } } &.loggedIn { .gameContainer { display: block; } }
위의 scss에서 body 클래스에 이름을 주는 처리는 아래 login.js에서 처리
// login.js import { initSockets } from './sockets'; const body = document.querySelector('body'); const loginForm = document.getElementById('jsLogin'); const NICKNAME = 'nickname'; const LOGGED_OUT = 'loggedOut'; const LOGGED_IN = 'loggedIn'; const nickname = localStorage.getItem(NICKNAME); const logIn = nickname => { const socket = io('/'); //소켓 연결 socket.emit(window.events.setNickname, { nickname }); //socketController로 보냄 initSockets(socket); //소켓 저장 }; if (nickname === null) { body.className = LOGGED_OUT; } else { body.className = LOGGED_IN; logIn(nickname); } const handleFromSubmit = event => { event.preventDefault(); const input = loginForm.querySelector('input'); // 입력한 value 값 const { value } = input; input.value = ''; localStorage.setItem(NICKNAME, value); // 입력한 value값 localStorage에 저장 body.className = LOGGED_IN; // body 클래스명 loggedIn으로 변경 -> 채팅창 보이고, 닉네임 창 사라짐 logIn(value); }; if (loginForm) { loginForm.addEventListener('submit', handleFromSubmit); //닉네임 submit 이벤트 발생 }
events.js : socket 전역 이벤트를 설정하기 위한 events 객체 생성 및 export
const events = { setNickname: 'setNickname', newUser: 'newUser', byeUser: 'byeUser', disconnect: 'disconnect', setMessage: 'setMessage', receiveMessage: 'receiveMessage' }; export default events;
socketController.js : socket 이벤트를 받아 socket.js로 보내고 socket.js에서 해당하는 이벤트를 찾아 handler를 실행하여 처리
// socketController.js import events from './events'; const socketController = socket => { const broadcast = (event, data) => socket.broadcast.emit(event, data); socket.on(events.setNickname, ({ nickname }) => { broadcast(events.newUser, { nickname }); socket.nickname = nickname; }); socket.on(events.disconnect, ({ nickname }) => { broadcast(events.byeUser, { nickname: socket.nickname }); }); // 유저가 메시지 입력하면 자신 이외 사용자에게 socket.on(events.setMessage, ({ message }) => { broadcast(events.receiveMessage, { message, nickname: socket.nickname }); }); }; export default socketController;
sockets.js : socketController.js에서 보내온 이벤트를 받아서 handler 처리
// sockets.js import { handleNewUser, handleByeUser } from './notifications'; import { handleReceiveMessage } from './chat'; let socket = null; export const getSocket = () => socket; // 이후에 socket 가졍오기 위한 함수 export const updateSocket = aSocket => { socket = aSocket; }; // 로그인 한 시점에 socket을 시작하도록 export const initSockets = aSocket => { const { events } = window; updateSocket(aSocket); aSocket.on(events.newUser, handleNewUser); aSocket.on(events.byeUser, handleByeUser); aSocket.on(events.receiveMessage, handleReceiveMessage); //본인 이외 다른 유저에게 보여질 메시지 소켓 };
notifications.js : 유저 입장시 좌측 하단에 알림 발생
const notifications = document.getElementById('jsNotifications'); const fireNotification = (text, color) => { const notification = document.createElement('div'); notification.innerText = text; notification.style.backgroundColor = color; notification.className = 'notification'; notifications.appendChild(notification); }; export const handleNewUser = ({ nickname }) => { fireNotification(`${nickname} 님 입장!`, 'rgb(0, 122, 255)'); }; export const handleByeUser = ({ nickname }) => { fireNotification(`${nickname} 님 퇴장!`, 'rgb(255, 149, 0)'); };
notifications.scss
.notification { position: absolute; left: 50px; bottom: 0px; opacity: 0; box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); border-radius: 20px; padding: 10px 20px; background-color: white; font-size: 20px; color: white; animation: notifi 0.5s ease-out forwards, dissapear 5s ease-in forwards; } @keyframes notifi { 0% { transform: translateY(30px); opacity: 0; } 10% { transform: translateY(-45px); opacity: 1; } 100% { transform: translateY(-30px); opacity: 1; } } @keyframes dissapear { from { opacity: 1; } to { opacity: 0; } }
chat.js : 채팅 submit 이벤트 발생시 처리
import { getSocket } from './sockets'; const messages = document.getElementById('jsMessages'); const sendMessage = document.getElementById('jsSendMessage'); const appendMessage = (text, nickname) => { const li = document.createElement('li'); li.className = `${nickname ? 'out' : 'self'}`; li.innerHTML = ` <span class="author ${nickname ? 'out' : 'self'}">${nickname ? nickname + ' : ' + text : text} </span> `; messages.appendChild(li); messages.scrollTop = messages.scrollHeight; }; const handleSendMessage = event => { event.preventDefault(); const input = sendMessage.querySelector('input'); const message = input.value; input.value = ''; appendMessage(message); // 나 이외 전체 사용자에게 broadcast const socket = getSocket(); socket.emit(window.events.setMessage, { message: message }); }; if (sendMessage) { sendMessage.addEventListener('submit', handleSendMessage); } // 본인 이외에 유저가 보낸 메시지를 받음 export const handleReceiveMessage = ({ nickname, message }) => { appendMessage(message, nickname); };
chat.scss
.chat { display: flex; flex-direction: column; align-items: center; position: relative; .chat__messages { padding: 10px 8px; display: flex; flex-direction: column; width: 240px; height: 70%; height: 55vh; overflow: scroll; box-shadow: 0px 0px 20px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); background-color: white; li:not(:last-child) { margin-bottom: 3px; } li.self { display: flex; justify-content: flex-end; } li.out { display: flex; justify-content: flex-start; } .author { display: flex; padding: 5px 10px; border-radius: 15px; max-width: 200px; font-size: 13px; color: white; } .author.self { justify-content: flex-end; box-shadow: 0px 0px 20px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); background-color: rgb(0, 122, 255); } .author.out { justify-content: flex-start; box-shadow: 0px 0px 20px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); background-color: rgb(90, 200, 250); } } .chat__form { position: relative; input { width: 250px; background-color: white; box-shadow: 0px 8px 20px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); padding-left: 20px; border-radius: 5px; } } }
li 태그와 span 태그에 self(본인), out(다른유저) 클래스를 구분지어서 appendMessage()에 text하나의 값만 전달되면 본인의 메시지 text만 text와 nickname이 들어온다면 다른 유저의 닉네임과 메시지를 보여줌으로 본인 메시지인지 아닌지 구분하여 클래스 이름을 self,out으로 구분지어주고 본인 메시지 작성시 채팅창 오른쪽에 표시 다른 유저의 메시지일 경우 왼쪽에 표시되게끔
'Programming > NodeJS' 카테고리의 다른 글
socket.io 원리 (0) 2020.03.31 passport-local, passport-local-mongoose 회원가입, 로그인 (0) 2020.03.21 Node.Js에서 AWS S3버킷에 파일 업로드 및 삭제 (0) 2020.03.19 Node.js Express 라우팅, 라우트 메소드 (GET,POST 요청 처리하기) (0) 2020.02.27 Passport 구글 로그인 인증 (oauth20) - nodeJS (0) 2020.02.26