Mi Lugarcito

Node.js - Twitter Clone Coding // Real time messages(Socket.IO) 본문

Express.js

Node.js - Twitter Clone Coding // Real time messages(Socket.IO)

selene park 2021. 4. 5. 13:10

medium.com/@vdongbin/socket-io-%EB%A7%9B%EB%B3%B4%EA%B8%B0-aa9fbf1a2eb7

 

Socket.io 맛보기

실시간 채팅 어플리케이션을 만들었을 때, Socket.io를 사용했던 기억을 더듬어 이 글을 작성해보려 합니다. 이 글에서는 간단한 개념과 예제를 다뤄보면서 Socket.io에 입문 지식 정도를 다뤄보겠습

medium.com

 

 

 

https://cdn.socket.io/3.1.3/socket.io.min.js" integrity="sha384-cPwlPLvBTa3sKAgddT6krw0cJat7egBga3DJepJyrLl4Q9/5WLra3rrnMcyTyOnh" crossorigin="anonymous​
npm install socket.io // 설치하기

socket.io/docs/v3/client-installation/index.html

 

Client Installation

CompatibilitySocket.IO does support IE9 and above. IE 6/7/8 are not supported anymore. Browser compatibility is tested thanks to the awesome Sauce Labs platform: Release notesThe release notes of eac

socket.io

 

 

https://cdn.socket.io/3.1.3/socket.io.min.js

 

 

 

 

 

 

크롬 접속시

사파리 접속시

 

 

app.js

//server part here
const express = require('express');
const app = express();
const port = 3003;
const middleware = require('./middleware')
const path = require('path')
const bodyParser = require("body-parser")
const mongoose = require("./database");
const session = require("express-session");

const server = app.listen(port, () => console.log("Server listening on port " + port));
//socket io server build...
const io = require("socket.io")(server, { pingTimeout: 60000 });

app.set("view engine", "pug");
app.set("views", "views");

app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, "public")));

app.use(session({
    secret: "bbq chips",
    resave: true,
    saveUninitialized: false
}))

// Routes
const loginRoute = require('./routes/loginRoutes');
const registerRoute = require('./routes/registerRoutes');
const postRoute = require('./routes/postRoutes');
const profileRoute = require('./routes/profileRoutes');
const uploadRoute = require('./routes/uploadRoutes');
const searchRoute = require('./routes/searchRoutes');
const messagesRoute = require('./routes/messagesRoutes');
const logoutRoute = require('./routes/logout');

// Api routes
const postsApiRoute = require('./routes/api/posts');
const usersApiRoute = require('./routes/api/users');
const chatsApiRoute = require('./routes/api/chats');
const messagesApiRoute = require('./routes/api/messages');

app.use("/login", loginRoute);
app.use("/register", registerRoute);
app.use("/posts", middleware.requireLogin, postRoute);
app.use("/profile", middleware.requireLogin, profileRoute);
app.use("/uploads", uploadRoute);
app.use("/search", middleware.requireLogin, searchRoute);
app.use("/messages", middleware.requireLogin, messagesRoute);

app.use("/api/posts", postsApiRoute);
app.use("/api/users", usersApiRoute);
app.use("/api/chats", chatsApiRoute);
app.use("/api/messages", messagesApiRoute);
app.use("/logout", logoutRoute);

app.get("/", middleware.requireLogin, (req, res, next) => {

    var payload = {
        pageTitle: "Home",
        userLoggedIn: req.session.user,
        userLoggedInJs: JSON.stringify(req.session.user),
    }

    res.status(200).render("home", payload);
})
//socket io - server part
//이렇게 세팅해도 실행안됨. 클라이언트에서도 세팅을 해줘야한다. 
io.on("connection", socket => {//socket 의미 : client 를 뜻함
    //console.log("connected to socket io");
    //event listener register 하기
    //callback function = arrow function..?
    //this part is event and "setup" is just calling name of event call..

    //3. 서버쪽에서 client가 넘겨준 userLoggedIn를 받아서 userData 로 넘겨준다. 
    socket.on("setup", userData => {
        //console.log(userData.firstName)
        socket.join(userData._id);//-4, room 만들고
        socket.emit("connected");//-5, connected 한다. 그리고 나서 클라이언트가 이 이벤트를 받으면
    })
    //클라이언트가 던졌으니 서버에서 받아야함
    socket.on("join room", room => socket.join(room));//room은 chatId 를 뜻함
    socket.on("typing", room => socket.in(room).emit("typing"));//-2, in means inside

})

chatPage.js

//client part!!!
//const { Socket } = require("socket.io"); //이제 더이상 클라이언트가 접속해도 여기 콘솔에 접속했다는 메시지 안뜬다!

$(document).ready(() => {
    //join the room 
    socket.emit("join room", chatId);//server 에게 join room 라고 알려주고 chatId 넘겨주면서 서버에게 받을 준비하라고 알려주기
    socket.on("typing", () => console.log("user is typing...."));


    $.get(`/api/chats/${chatId}`, (data) => $("#chatName").text(getChatName(data)))

    //loading 새로 했을때 창에 보이도록 하기  
    $.get(`/api/chats/${chatId}/messages`, (data) => {
        //console.log(data);
        var messages = [];

        //상대방 대화 끝나고 프로필 사진 보여주기
        var lastSenderId = "";

        data.forEach((message, index)=>{//index를 한 덩이로 0,1,2,3...이렇게 숫자로 구분//---1
            var html = createMessageHtml(message, data[index+1], lastSenderId);//data는 무조건 배열 [] 로 표기해줘야함!!
            messages.push(html);//여기서 messages는 array 이다!

            lastSenderId = message.sender._id;
        })

        var messagesHtml = messages.join("");//messagesHtml 는 huge array, one big string
        addMessagesHtmlToPage(messagesHtml);
        scrollToBottom(false);

        $(".loadingSpinnerContainer").remove();
        $(".chatContainer").css("visibility", "visible");
    })

})

$("#chatNameButton").click(() => {
    var name = $("#chatNameTextbox").val().trim();
    
    $.ajax({
        url: "/api/chats/" + chatId,
        type: "PUT",
        data: { chatName: name },
        success: (data, status, xhr) => {
            if(xhr.status != 204) {
                alert("could not update");
            }
            else {
                location.reload();
            }
        }
    })
})

$(".sendMessageButton").click(() => {
    messageSubmitted();
})

//send the notification to the socket을 위해서 코드 추가
$(".inputTextbox").keydown((event) => {
    
    updateTyping();

    if(event.which === 13) {
        messageSubmitted();
        return false;
    }
})

//send the notification to the socket을 위해서 코드 추가
function updateTyping(){
    socket.emit("typing", chatId);//-1
}







//채팅 메시지 화면에 뿌리기
function addMessagesHtmlToPage(html){
    $(".chatMessages").append(html);
    
    //TODO : SCROLL TO BOTTOM
}

function messageSubmitted() {
    var content = $(".inputTextbox").val().trim();

    if(content != "") {
        sendMessage(content);
        $(".inputTextbox").val("");
    }
}

function sendMessage(content) {
    $.post("/api/messages", { content: content, chatId: chatId }, (data, status, xhr) => {
       
        if(xhr.status !=201){
            alert("Could not send message");
            $(".inputTextbox").val(content);
            return;
        }
       
        //console.log(data);
        addChatMessageHtml(data);
    })
}

function addChatMessageHtml(message){
    if(!message || !message._id){
        alert("Message is not valid");
        return;
    }

    var messageDiv = createMessageHtml(message, null, "");//3

    //$(".chatMessages").append(messageDiv);
    addMessagesHtmlToPage(messageDiv);

}

function createMessageHtml(message, nextMessage, lastSenderId){//2

    var sender = message.sender;
    var senderName = sender.firstName + " " + sender.lastName;

    var currentSenderId = sender._id;
    var nextSenderId = nextMessage != null ? nextMessage.sender._id : "";

    //중요 2줄!!
    var isFirst = lastSenderId !==currentSenderId;//즉 본인을 뜻 
    var isLast = nextSenderId !==currentSenderId;

    var isMine = message.sender._id === userLoggedIn._id;
    var liClassName = isMine ? "mine" : "theirs";


    var nameElement = "";



    //이 아래 2개의 로직은 메시지 1개만 보냈을때 first 이자 last 일 수 있다. 
    if(isFirst){
        liClassName += " first";

        if(!isMine){
            nameElement = `<span class='senderName'>${senderName}</span>`;
        }
    }

    var profileImage = "";
    if(isLast){
        liClassName += " last";
        profileImage = ` <img src='${sender.profilePic}'>`;
    }

    var imageContainer = "";
    if(!isMine){
        imageContainer = `<div class='imageContainer'>
                            ${profileImage}
                          </div>`;
    }

    return `<li class='message ${liClassName}'>
                ${imageContainer}
                <div class='messageContainer'>
                    ${nameElement}
                    <span class ='messageBody'>
                        ${message.content}
                    </span>
                </div>
            </li>`;
}

function scrollToBottom(animated){
    var container = $(".chatMessages");
    var scrollHeight = container[0].scrollHeight;

    if(animated){
        container.animate({scrollTop : scrollHeight}, "slow");
    }else{
        container.scrollTop(scrollHeight);
    }
}

clientSocket.js

//클라이언트 소켓 -- client part
//socket instance 만들려면 socket io 를 만들어야 한다
var connected = false;
//1. 클라이언트가 soket io 접속 
var socket = io("http://localhost:3003");
//2. client 가 "setup" 이벤트 리스너를 받아서 userLoggedIn 을 서버로 보내주면
socket.emit("setup", userLoggedIn);
//6. 클라이언트가 connected 이 이벤트를 받으면 우리는 소켓연결 성공했다는것을 알 수 있고 connected = true라고 할 수 있다. 
socket.on("connected", () => connected = true)