로그인을 한 사람에게만 채팅 가능하도록 만들기
-> auth.js 미들웨어를 추가
server.js - auth 추가
app.get('/chat', auth, (req, res) => {
res.render('chat.html')
})
auth.js - res.redirect 를 -> res.json으로 바꾸기
토큰이 없으면 (로그인이 안되었으면 ) -> res 응답을 json 으로 보낸다 {result:false, msg:'로그인이 필요합니다'} 라는 객체를 (요 객체는 express가 자동으로 String으로 변환해서 보내준다.)
let {AccessToken} = req.cookies // 클라이언트의 cookie.accesstoken
if(AccessToken == undefined){
console.log('로그인을 진행해주세요')
//res.redirect('/?msg=로그인을 진행해주세요')
res.json({result:false, msg:'로그인이 필요합니다'})
return
}
쿠키 값을 지우고 localhost:3000 -> 채팅 버튼을 누르면 위의 내용이 String값으로 그대로 뜬다.
chat.js
const chatBtn=document.querySelector('#chatBtn');
const chatRoom = document.querySelector('#chatRoom');
chatBtn.addEventListener('click',async ()=>{
let url = 'http://localhost:3000/chat'
let options = {
method:'GET'
}
let response = await fetch (url,options);
let result = await response.text();
chatRoom.innerHTML=result;
})
fecth 로 가져온 promise 객체 반환값에 .text() 로 안 body의 내용 그대~로 가져옴=> 위의 브라우저에서 쓴 그대로
만약 body의 내용을 string으로 바로 가져다 쓸 때는 .json() 사용
성공시에는 html 을 넘겨주고 (html - text 형태 )
실패시에는 json 형태로 응답해준다
-> 요걸 사용해서 json 타입이면 - 실패 / 아니면 - 성공 알고리즘을 만들기
chat.js
chatBtn.addEventListener('click',async ()=>{
let url = 'http://localhost:3000/chat'
let options = {
method:'GET'
}
let response = await fetch (url,options);
let res_body = await response.text();
if(isJson(res_body)){ // 응답이 json인 경우 : 로그인 실패한 경우
let {result,msg} = JSON.parse(res_body);
if (result==false) alert(msg);
return
}else{
// 응답이 html 인 경우 (render잘 되면) : 로그인 성공한 경우
chatRoom.innerHTML=res_body;
}
})
function isJson(str){
try{
let json = JSON.parse(str);
return (typeof json =='object') //return : True
}catch(e){
return false;
}
}
여기까지의 내용:
'채팅' 버튼을 클릭하면 -> fetch로 /chat 요청하고 -> 받은 응답res_body를 함수 isJson에 실행
1. (로그인 상태) 받은 응답이 html 이라면 -> JSON.parse 불가 -> catch -> return false -> res_body(응답을) chatRoom HTML로 넣기
2. (비로그인 상태) 받은 응답(res_body)를 JSON.parse 를 하고 그 값의 타입이 '객체'라면 return True -> parse 해서 그 안의 result, msg 키값을 변수에 저장 후 , 사용
.text() .json() 비교
json -> Text형태로 바꾸기 : JSON.stringify()
Text -> json 형태로 바꾸기 : JSON.parse()
로그인 후
console.log(response)
console.log(res_body) <---response.text()
-> 여기서 res_body는 html text형태라 JSON.parse 가 안됨
로그인 안했을 때
console.log(response)
console.log(res_body)) < --- = response.text()
console.log(JSON.parse(res_body) -- res_body 가 객체 형태 String 이어서 JSON.parse 가능
.text()말고 json()으로 작성했을 때 (대신 로그인을 안한 상태여야함. html이 들어오면 안되므로 )
let response = await fetch (url,options);
console.log(response)
let res_body = await response.json();
console.log(res_body)
String이 아닌 json 객체 형태로 들어와서 바로 안의 key값으로 value에 접근 가능
즉, a= {String(객체형태)}.text(); JSON.parse(a) == a = {Stirng(객체형태)}.json
auth.js - res.json 으로 수정 / token 삭제 추가
if(sign != signature){
console.log('부적절한 토큰')
//res.redirect('/?msg=부적절한 토큰')
res.clearCookie('AccessToken');
res.json({result:false,msg:'부적절한 토큰입니다.'})
return
}
let {userid,exp} = JSON.parse(Buffer.from(payload,'base64').toString())
let nexttime = new Date().getTime();
if(nexttime > exp){
console.log('토큰 만료')
res.clearCookie('AccessToken')
//res.redirect('/?msg=토큰만료')
res.json({result:false, msg:'토큰만료'})
return
}
req.userid = userid;
next()
토큰이 일치하지 않을 때, 토큰이 만료 되었을 때 응답을 redirec에서 -> res.json으로 수정 / token 삭제 추가
-> 토큰 network 에서 임시로 변경해서 '채팅'버튼 다시 눌러봐 -> 부적절한 토큰 alert가 뜸
채팅 한번 더 click 하면 닫히도록 만들기
chat.js
let flag=undefined;
chatBtn.addEventListener('click',async ()=>{
switch(flag){
case true:
chatRoom.style.display="none";
break;
case false:
chatRoom.style.display="block";
break;
case undefined:
getChatRoom()
break;
}
})
async function getChatRoom(){
let url = 'http://localhost:3000/chat'
let options = {
method:'GET'
}
let response = await fetch (url,options);
let res_body = await response.text();
if(isJson(res_body)){ // 응답이 json인 경우 : 로그인 실패한 경우
let {result,msg} = JSON.parse(res_body);
if (result==false) alert(msg);
return
}else{
// 응답이 html 인 경우 (render잘 되면) : 로그인 성공한 경우
chatRoom.innerHTML=res_body;
}
}
click Event의 내용을 다른 function으로 옮기고 (코드가 너무 복잡해져서) -> 이 때 ** 옮기는 새로운 함수 : getChatRoom 에도 async 를 붙여줘야함
flag 전역변수 =undefined 를 주고 맨 처음 load 되었을 때 flag =undefined -> switch 문에서 case undefined 가 실행 -> 함수 실행
이 경우 버튼 클릭할 때마다 network에 req가 찍힘 : fetch 요청은 url 변경과 같음 (req & res 발생)
-> 서버의 소스 낭비 => 아래처럼 수정
chatBtn.addEventListener('click',async ()=>{
switch(flag){
case true:
flag = false;
chatRoom.style.display="none";
break;
case false:
flag = true;
chatRoom.style.display="block";
break;
case undefined:
flag = true;
getChatRoom()
break;
}
})
-> 한번 클릭하면 undefined -> true (열린 상태 ) 그뒤로 flag 값과 style 값만 변경 (req,res 는 없다! -> 리소스 효율화)
css 입히기
public - css - chat.css 생성
index.html 에 css 연결
<link href="/css/chat.css" rel="stylesheet">
chat.html div 추가
<div id="chat">
<div>
<span class="me">안녕하세요</span>
</div>
<div>
<span class="you"> 네 안녕 </span>
</div>
</div>
<div id="chatInputArea">
<input type="text" id="msg">
<button id="chatSend">전송</button>
</div>
public-css-chat.css 코드 작성
#chat{
width:400px;
height:500px;
background:wheat;
}
#chat>div>span{
display:inline-block;
padding:7px 14px;
border-radius:15px;
}
#chat>div{
height:30px;
padding:5px;
}
#chat>div>span.me{
float:right;
background-color: yellow;
}
#chat>div>span.you{
float:left;
background-color: white;
}
#chatInputArea{
width:400px;
padding:5px 7px;
}
#chatInputArea>input{
width:200px;
padding:7px 14px;
border:1px solid 999999;
}
#chatInputArea>button{
padding:7px 14px;
background-color: #eee;
border:1px solid #999999;
}
그럴듯하게 만들어짐
오른쪽 노란색 : 내가 보낸 거
왼쪽 흰색 : 상대방이 보낸 거
쏘켓 연결
$npm i socket.io http
server.js - 위의 packages 가져오는 코드 / 설정 코드/ app.listen-> server로 작성
const socket = require('socket.io');
const http = require('http');
const server = http.createServer(app);
const io = socket(server);
Server.listen(3000, () => { /// ***http를 가져와서 server를 만들면 꼭 server로 변경하기!!
console.log('server start port: 3000');
})
Client쪽에 연결하기
index.html
<script type="text/javascript" src ="/socket.io/socket.io.js"></script>
성공
만약 안되면 저번시간에 한 것처럼 node_modules 경로 하나하나 설정 & server.js 에 app.use(express.static('node_modules') ... 이런거 써야함
성공했으니 이제 Client가 handshake to Server (소켓의 요청은 항상 Client !) 요청하는걸 Handshake라고 함
귀엽다,,
Client 가 REQ 요청을 보낼 때는 (login 상태) REQ에 cookie 도 들어 있어서 server가 이를 복구화 시켜서 userid를 알아낼 수 있다.
chat.js
로그인 성공한 경우에다가 함수 만들어서 socket 연결 요청 코드 작성
const socket = io();
- 위의 코드는index.html에서 socket과 연결 script를 작성했기때문에 가능
}else{
// 응답이 html 인 경우 (render잘 되면) : 로그인 성공한 경우
chatRoom.innerHTML=res_body;
socketChat();
}
}
function socketChat(){
socket.on('connect', ()=>{})
}
server.js
io.sockets.on('connection', (socket)=>{
console.log(socket.handshake);
})
101 -> 소켓 연결 잘됨
socket.handshake 내용
{
headers: {
host: 'localhost:3000',
connection: 'keep-alive',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
accept: '*/*',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
cookie: 'user=hh; AccessToken=eyJ0cHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJhc2RmIiwiZXhwIjoxNjIyNjUzOTU0NjA2fQ.FhmQZyd4mppRMPzv8wnqsGmZgzfvJJqIGFiAKUAFMqQ'
},
time: 'Thu Jun 03 2021 00:45:57 GMT+0900 (대한민국 표준시)',
address: '::1',
xdomain: false,
secure: false,
issued: 1622648757613,
url: '/socket.io/?EIO=4&transport=polling&t=NdDPnqq',
query: [Object: null prototype] {
EIO: '4',
transport: 'polling',
t: 'NdDPnqq'
},
auth: {}
}
안에 client가 handshake를 보낼 때 보내는 REQ 요청 안에 handshake라는 키값 안에 위 내용들이 들어있다.
hadnshake 안 headers 에 cookie 가 있다. 해당 토큰값을 복구화 해서 payload안에 있는 userid값을 가져와야함
* 실무에서는 쿠키값이 토큰말고 다른 것들이 함께 담겨있는 경우가 많다고 한다. -> 쿠키값 임의로 더 추가해주기
network - application - 더블클릭으로 추가/ 수정
더블클릭이 안되면 console.log로 추가
server.js
let id;
io.sockets.on('connection', (socket)=>{
let cookieString = socket.handshake.headers.cookie;
if(cookieString!=undefined){ // cookieString이 존재하면 = 로그인한 경우면
let cookieArr=cookieString.split(';');
cookieArr.forEach(v=>{
let [key, value] = v.trim().split('=');
if(key=='AccessToken'){
let payload = value.split('.')[1];
let {userid,exp} = JSON.parse(Buffer.from(payload,'base64').toString());
id = userid;
}
})
console.log(id)
}
})
크롬, 파이어폭스 각각 브라우저 열어서 다른 id로 로그인 , console.log확인 -> 각각 다른 id가 뜬다
전송 버튼 눌러서 채팅 뜨도록 만들기
평소 js 에 따로 넣었는데 chat.js 처음 page 로딩되었을 때 실행되게 됨. document.querySelector('#msg') 이렇게 가져와도 NULL 값이 뜬다. 가장 처음 index.html -> 채팅 버튼 눌러야 chat.html 이 render 됨
해결 => chat.html -onclick 이벤트를 html에 그대로 주기
chat.html
<div id="chatInputArea">
<input type="text" id="msg">
<button id="chatSend" onclick="send()">전송</button>
</div>
chat.js (chat.html 과 연결된 브라우저 js, Client)
채팅창의 value 값을 send 보내기 + msgAdd 함수이용하여 내가 보낸 값 class 'me'로 appendChild하기
function send(){
const msg = document.querySelector('#msg');
socket.emit('send', msg.value);
msgAdd(msg.value, 'me');
}
function msgAdd(msgValue, who){
const div = document.createElement('div');
const span = document.createElement('span');
const chat = document.querySelector('#chat');
span.innerHTML=msgValue;
span.classList.add(who);
div.appendChild(span)
chat.appendChild(div)
}
server.js
서버에서 받은 내용 다른 clients 들에게 뿌리기 - broadcast
let {userid,exp} = JSON.parse(Buffer.from(payload,'base64').toString());
id = userid;
}
})
console.log(id)
}
socket.on('send',(data)=>{
console.log(data);
socket.broadcast.emit('msg', data);
})
})
chat.js
받은 'msg'로 받은 data 를 'you' class로 appendChild 함수로 ㄱ ㄱ
function socketChat(){
socket.on('connect', ()=>{});
socket.on('msg', (data)=>{
msgAdd(data,'you')
})
}
이제 채팅 안읽음 표시 구현하기
index.html - button 에 data-value를 자바스크립트로 만들어서 수가 올라가도록 만들기
html = data-value 는 js의 dataset.value를 의미
chat.js
function socketChat(){
socket.on('connect', ()=>{});
socket.on('msg', (data)=>{
chatBtn.dataset.value = parseInt(chatBtn.dataset.value)+1;
if(flag==false){
chatBtn.innerHTML=`채팅<span style="coler:red; padding:20px;">${chatBtn.dataset.value}</span>`
}else{
}
msgAdd(data,'you')
})
}
-
chat.js 윗부분
chatBtn.addEventListener('click', ()=>{
switch(flag){
case true:
flag = false;
chatRoom.style.display="none";
break;
case false:
chatBtn.innerHTML=`채팅`;
chatBtn.dataset.value=0;
flag = true;
chatRoom.style.display="block";
break;
case undefined:
flag = true;
getChatRoom()
break;
}
})
index.html
<button id = "chatBtn" data-value="0" >채팅</button>
문제점
채팅 버튼 클릭을 해야 -> 채팅창html render -> socket 연결요청/ 연결되어 -> 채팅 dataset value 가 보임
브라우저가 '채팅'을 클릭하기 전에는 채팅 몇개와있는지 안보임