본문 바로가기

블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기

[56일차]20210602 Javascript websocket 채팅구현하기 / 자바스크립트 웹소켓 node.js express 채팅 / 채팅 안읽음 표시 구현

반응형

 

 

로그인을 한 사람에게만 채팅가능하도록 만들기 

-> 미들웨어 auth.js - 토큰값없을 경우 저 버튼이 실행되지 않도록 

 

 

 

 

server.js  -  auth 추가 

app.get('/chat',auth, (req,res)=>{
    res.render('chat.html')
})

 

server on -> check -> consol -> '로그인을 진행해주세요'

 

 

 

auth.js

json 타입으로 값을 넘겨주고 싶음/ 어떻게 안되었는지 text를 보내기 

    if(AccessToken == undefined){
        console.log('로그인을 진행해주세요')
        //res.redirect('/?msg=로그인을 진행해주세요')
        res.json({result:false, msg:'로그인이 필요합니다'})
        return
    }

 

저 json은 객체처럼 생겼어도 스트링으로 보내는 것 (교환하는 값은 무조건 TEXT) 

express가 알아서 json을 스트링으로 변환시켜서 보냄

.text() 로 body를 받은 경우의 결과값 

 

 

chat.js

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();
    console.log(response);
    console.log(result);
    chatRoom.innerHTML=result;
})

설명 : 

response.json() - body에 있는 string 값 바로 스트링으로 가져다 쓸 떄.

response.text() - body에 있는 내용을 있는 그대로 받을 때 (날 것 그 대ㅐ로) (위의 사진) 

 

result 값이 실패 시에는 -> json 형태

성공할 때는 html - text형태로 온다. 

 

* json 타입인가 아닌가를 찾는 함수 만들기 == 성공했나 실패했나 

 

text---> json 형태로 바꾸는 법 - JSON.parse()

json---> text : JSON.stringify()

 

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();

    if(isJson(result)){
        //로그인처리 잘 안되었을 때(json일 떄 from auth) => True
        let json = JSON.parse(result);
        if(json.result == false) alert(json.msg);
        return 
    }else{
        //로그인처리 완료되었을 때 => False
        chatRoom.innerHTML=result;
    }
})

function isJson(str){
    //Ture or False 값만 나오게 
    try{
        let json = JSON.parse(str)
        return (typeof json == 'object') //맞으면 True,틀리면 False
    }catch(e){
        //str= 'adsfsafdasdf' 경우 try 문을 실행할 수 없음
        //json으로 변환할 수 업성서
        return false;
    }
}

function isJson에서 chatBtn을 click했을때 fetch('/chat')으로 얻은 값이 function isJson을 통해 객체로 판정되면 (보내준게 html 이 아니기 때문에- 로그인 실패 ) 라면 return  : True /  true 라면 받은 string 값을 JSON으로 바꿔서 그 안의 result 값이 false 라면 보내온 msg에 있는 값을 alert 해라  

 

 

똑같은 아래 두 코드 

        if(json.result == false) alert(json.msg);
        if(!json.result) alert(json.msg);

 

 

여기까지 server on -> check

 

 

 

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
    }

 

server on -> 로그인 -> network - application 쿠키 토큰값 생김 -> 채팅 눌러보기 

 

쿠키 내용 봐봐 (in application) -> 쿠키 내용을 임의로 바꾸면 -> 로그인이 되지 않아야해 ==로그인이 안된상태랑 똒같이 처리해야함 

 

토큰 값을 바꾸고 다시 '채팅' 을 누르면

 

이렇게됨. 

누군가 악의적으로 토큰값 수정하면 로그인 안되게 처리함 by예전에 작업한 요 코드

 

 

 

 

채팅 한번 click하면 닫히는거 처리해보기 

-> 로그인창과 다르게 해볼꺼야

 

chat.js

 

flag = undefined; 선언 / 새로운 함수 만들어서 addEvent 내용 붙여넣기 / 

flat의 값에 따라 (구분값) 

 

let flag = undefined; 

chatBtn.addEventListener('click', ()=>{
    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;
    }
})

async function getChatRoom(){
    let url = 'http://localhost:3000/chat'
    let options = {
        method:'GET'
    }
    let response = await fetch (url,options);
    let result = await response.text();

    if(isJson(result)){
        //로그인처리 잘 안되었을 때(json일 떄 from auth) => True
        //isJson
        let json = JSON.parse(result);
        if(json.result == false) alert(json.msg);
        return 
    }else{
        //로그인처리 완료되었을 때 => False
        chatRoom.innerHTML=result;
    }
}

 

server on - check 

 

 

채팅을 누르면 req 가 계속 늘어남  -> 이런 식은 좋지않음 -> 그래서 한번만 가져와서 style none, block 으로 만든 것

fetch 라는 것은url 변경과 같음 (req와 res 가 일어남) 요 요청을 계속 채팅 클릭할 떄마다 생기게하면 안좋으닝아래 처럼 ㄱ ㄱ 

** 아주 중요한 개념 

 

chatBtn.addEventListener('click', ()=>{
    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;
    }
})

 

요렇게 하면 req가 딱 한번 (맨 처음) 일어남. - 서버의 리소스 낭비 줄임 

 

 


 

 

css 입혀보기 

 

css - chat.css 파일 만들기 

 

index.html 에 연결

    <title>INDEX.html</title>
    <script type="text/javascript" src="/js/main.js"></script>
    <link href="/css/main.css" rel="stylesheet">
    <link href="/css/chat.css" rel="stylesheet">
</head>

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:600px;
    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:left;
    background-color: yellow;
}

#chat>div>span.you{
    float:right;
    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;
}


 

 

 

교수님의 CSS KNOW-HOW

 

333333 찐함

666666

999999 연함

eeeeee 

efefef

dddddd

ededed

 


 

쏘켓타임

 

$npm i socket.io http

server.js 에 가져오는 코드 / 설정 코드 작성 / app -> server.listen 으로 수정 

const socket = require('socket.io');
const http = require('http');

const server = http.createServer(app);
const io = socket(server);

.
.
.

server.listen(3000,()=>{
    console.log('server start port: 3000');
}) 

 

 

client 쪽에도 연결하기

index.html

<script type="text/javascript" src="/socket.io/socket.io.js"></script>

위의 코드가 실행안되면 저번처럼 node_modules 에 접착해야함.

될 때도, 안될 때도 있다고 함 - 그 이유는 미지수,, 

clien 쪽에 쏘켓 잘 연결되었나 확인 방법

 

저 코드만으로 잘 연결 되었다. 

 

최초의 요청, 응답 : Handshake 

Client ---------REQ (쿠키) -------> Server 

쿠키도 같이 보내줘서 server가 쿠키 사용할 수 있음. 

 

server.js

io.sockets.on('connection',socket=>{
    console.log(socket.handshake);
})

진짜 socket안에 handshake가 있네,,

 

chat.js

const socket = io();

-

        return 
    }else{
        //로그인처리 완료되었을 때 => False
        chatRoom.innerHTML=result;
        socketChat()
    }
}

function socketChat(){
    socket.on('connect',()=>{})
}

-

 

코드를 따로 뺐다. 보기 쉽게 / 넣은 위치는 로그인 잘 완료 되었으 때

server on -> check 

101 잘 떳는지췤

 

 

console.log(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: 'AccessToken=eyJ0cHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJhc2RmIiwiZXhwIjoxNjIyNjA1MTQ2NzcxfQ.j2TjcoW5kWpM5achLpaI91WFV0Gx0hqAkg%2Bu2CvrEjo'
  },
  time: 'Wed Jun 02 2021 11:29:58 GMT+0900 (대한민국 표준시)',
  address: '::1',
  xdomain: false,
  secure: false,
  issued: 1622600998018,
  url: '/socket.io/?EIO=4&transport=polling&t=NdAZbnS',
  query: [Object: null prototype] {
    EIO: '4',
    transport: 'polling',
    t: 'NdAZbnS'
  },
  auth: {}
}

 

req.에 접근해야 user의 정보를 알 수 있음. 

우리는 지금 그 바깥에서 작업한거라 user id 값을 알 수 없음

handshake 안에 Cookie 값이 있음 ( 요 안에 AccessToken 값이 있음) - 어딘가에 숨겨져있고 복구화를 ㅎㅐ야한다. 

AccessToken 값 = 그냥 STRING  -> 복구화 (id값이 있는 payload 필요) 

 

- 해당 값을 .로 split해서 payload 가져오기

 

실제로 cookie값 안에는 로그인값 / 장바구니 / 등 쿠키가 여러개일 것이다라고 생각하고 작업하기 

 

임시로 생성하기 -> network 가서 그냥 만들어 

더블클릭으로 변경할 수 있는데 안되어서 아래 console.log로 추가해줌 

server.js

io.sockets.on('connection',socket=>{
    console.log(socket.handshake.headers.cookie);
})

server on -> check 

 

console 

AccessToken=eyJ0cHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJhc2RmIiwiZXhwIjoxNjIyNjA1MTQ2NzcxfQ.j2TjcoW5kWpM5achLpaI91WFV0Gx0hqAkg%2Bu2CvrEjo; user=hh

cookie 지금 2개 들어 있음 

cookie는 ; 를 기준으로 나뉘어져 있음 

 

 

 

cookie.js 만들어서 node cookie.js 명령어 실행으로 결과값 바로바로 확인해보기

let cookie = 'AccessToken=eyJ0cHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJhc2RmIiwiZXhwIjoxNjIyNjA1MTQ2NzcxfQ.j2TjcoW5kWpM5achLpaI91WFV0Gx0hqAkg%2Bu2CvrEjo; user=hh'
let cookieArr = cookie.split(';');


cookieArr.forEach(v=>{
    let [name,value] = v.split('=');
    if(name.trim() == 'AccessToken'){
        console.log(value);
        //이제 Accesstoken에서 payload 값 가져오기 
        let jwt = value.split('.');
        //payload: jwt[1] 이제 복구화 진행
        console.log(jwt[0])
        console.log(jwt[1])
        console.log(jwt[2])

        let payload = Buffer.from(jwt[1],'base64').toStirng();
        console.log(payload);
        let obj = JSON.parse(payload);
        console.log(obj.userid);
    }
})

 

forEach 대신 filter, map으로도 가능하다

let cookie = 'AccessToken=eyJ0cHkiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJhc2RmIiwiZXhwIjoxNjIyNjA1MTQ2NzcxfQ.j2TjcoW5kWpM5achLpaI91WFV0Gx0hqAkg%2Bu2CvrEjo; user=hh'
let cookieArr = cookie.split(';');


cookieArr.forEach(v=>{
    let [name,value] = v.split('=');
    if(name.trim() == 'AccessToken'){
        let jwt = value.split('.');
        let payload = Buffer.from(jwt[1],'base64').toString();
        let {userid} = JSON.parse(payload);
        console.log('첫번째방법 : ',userid);
    }
})
let userid1 = cookie.split(';').filter(x => x.indexOf('AccessToken')==0)
                    .map(v=>{
                        let [name,value]=v.trim().split('=');
                        
                        return JSON.parse(Buffer.from(value.split('.')[1], 'base64').toString()).userid; 
                    })
        console.log('두번째방법 : ', userid1)


 

 

요렇게,,

 

 

server.js 로 옮기기


let id;
io.sockets.on('connection', socket => {
    let cookieString = socket.handshake.headers.cookie;
    let cookieArr = cookieString.split(';');

    cookieArr.forEach(v => {
        let [name, value] = v.split('=');
        if (name.trim() == 'AccessToken') {
            let jwt = value.split('.');
            let payload = Buffer.from(jwt[1], 'base64').toString();
            let {userid} = JSON.parse(payload);
            id = userid;
        }
    })
    console.log('cc',id);
})

- if 추가 

let id;
io.sockets.on('connection', socket => {
    let cookieString = socket.handshake.headers.cookie;
    console.log(cookieString)
    if (cookieString != undefined) {//쿠키값이 없을 떈 실행하지 않겠다. 
        let cookieArr = cookieString.split(';');
        cookieArr.forEach(v => {
            let [name, value] = v.split('=');
            if (name.trim() == 'AccessToken') {
                let jwt = value.split('.');
                let payload = Buffer.from(jwt[1], 'base64').toString();
                let { userid } = JSON.parse(payload);
                id = userid;
            }
        })
        console.log(id);
    }
})

 

 

-> 크롬, 파이어폭스 두개 켜서 로그인 다른 아이디로 들어가서 console.log 를 확인 -> 각각의 id가 뜬다. 

가능한 이유 : handshake 요청되었을 때 각각 Clients들의 req with cookie을 해석해서 보여준 것이기 때문에 

 

 

 

 

 

 

chat.js 에서 event를 등록할 수가 없다. 

이렇게 chat.html 의 id 값을 chat.js 에 가져올 수가 없다. 

 

 

 

 

 

 

 

파일이 처음 로드가 되었을 때 chat.js 가 실행 -> chat.html 은 그 때 없음. 

 

-> 가장 간단한 방법 : chat.html에 직접 event를 줌 - onclick="send()"

chat.html

<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" onclick="send()">전송</button>
</div>

chat.js

function send(){
    const msg = document.querySelector('#msg');
    console.log(msg.value);
}

브라우저 console.log 확인 

 

 

 

 

내용 보내기 코드 

chat.js

function send(){
    const msg = document.querySelector('#msg');
    console.log(msg.value);
    socket.emit('send', msg.value);
}

server.html -connection 안에 

let id;
io.sockets.on('connection', socket => {
    let cookieString = socket.handshake.headers.cookie;
    if (cookieString != undefined) {//쿠키값이 없을 떈 실행하지 않겠다. 
        let cookieArr = cookieString.split(';');
        cookieArr.forEach(v => {
            let [name, value] = v.split('=');
            if (name.trim() == 'AccessToken') {
                let jwt = value.split('.');
                let payload = Buffer.from(jwt[1], 'base64').toString();
                let { userid } = JSON.parse(payload);
                id = userid;
            }
        })
        console.log(id);
    }

    socket.on('send', data=>{
        console.log(data) 
        socket.broadcast.emit('msg',data)
    })
})

chat.js

function send(){
    const msg = document.querySelector('#msg');
    console.log(msg.value);
    socket.emit('send', msg.value);
    addCard(msg.value, 'me');
}


//type = me or you 
function addCard(text, type){
    const div = document.createElement('div');
    const span = document.createElement('span');
    const chat = document.querySelector('#chat');

    span.innerHTML = text;
    span.classList.add(type);
    div.appendChild(span);
    chat.appendChild(div)
}

server on -check

 

chat.js

function socketChat(){
    socket.on('connect',()=>{});

    socket.on('msg', data=>{
        addCard(data,'you')
    })
}

 

 

 

 

 

카카오 메세지 받으면 읽음 처리

chat.js - >socket -> 에서 우리의 현 상태 (읽었는지) 를 파악하는걸 만들기

아까 한 flag 값 사용 

 

dataset

html 의 data-value="" -> 요게 dataset 해당 element에 어떤 값을 가지고 있는가 저장할 떄 

 

 

index.html - button에 data-value를 자바스크립트로 만들어서 수가 올라가도록 

chat.js

function socketChat(){
    socket.on('connect',()=>{});

    socket.on('msg', data=>{
        //html tag로 data-value 를 의미 == dataset.value
        chatBtn.dataset.value=1;
        if(flag==false){

        }else{

        }
        addCard(data,'you')
    })
}

 

 

보낸 사람말고 받은 사람만 value가 1 ; 이제 이걸 늘어나게 만들기

chat.js

function socketChat(){
    socket.on('connect',()=>{});

    socket.on('msg', data=>{
        //html tag로 data-value 를 의미 == dataset.value
        chatBtn.dataset.value= chatBtn.dataset.value+1;
        if(flag==false){

        }else{

        }
        addCard(data,'you')
    })
}

 

 

int 가 아니여서 111 이렇게 됨 

 

 

index.html

  <button id = "chatBtn" data-value="0">채팅</button>

chat.js

function socketChat(){
    socket.on('connect',()=>{});

    socket.on('msg', data=>{
        //html tag로 data-value 를 의미 == dataset.value
        chatBtn.dataset.value= parseInt(chatBtn.dataset.value)+1;
        if(flag==false){

        }else{

        }
        addCard(data,'you')
    })
}

counting됨

 

이제 채팅 버튼에 요 숫자를 표현하기

 

 

function socketChat(){
    socket.on('connect',()=>{});
    socket.on('msg', data=>{
        //html tag로 data-value 를 의미 == dataset.value
        chatBtn.dataset.value= parseInt(chatBtn.dataset.value)+1;
        if(flag==false){
            chatBtn.innerHTML=`채팅<span style="coler:red; padding:20px;">${chatBtn.dataset.value}</span>`
        }else{

        }
        addCard(data,'you')
    })
}

 

 

 

 

chat.js

chatBtn.addEventListener('click', ()=>{
    switch(flag){
        case true:
            //열린 상태에서 다시 누를 때 -> 닫히는 기능할 때 
            flag = false; 
            chatRoom.style.display='none';
        break;
        case false:
            //처음 제외하고 다시 열릴 때
            flag = true;
            chatBtn.innerHTML='채팅';
            chatBtn.dataset.value=0;
            chatRoom.style.display='block';
        break;

 

 

여기까지 하면 채팅 버튼 클릭해서 보면 해당 count 값은 없어짐 

 

 

오늘의 수업 끝,,,,

 

id  채팅창에 나오도록 ,,

jwt 인증 토큰으로 구현하기 

반응형