Skip to content

Latest commit

 

History

History
381 lines (325 loc) · 9.34 KB

File metadata and controls

381 lines (325 loc) · 9.34 KB

Socket.io 클라이언트 사용 예시

연결 방법

웹 클라이언트 (React/Next.js)

import io from 'socket.io-client';

// Socket 연결
const socket = io('ws://localhost:3001/chat', {
  auth: {
    token: localStorage.getItem('accessToken') // JWT 토큰
  }
});

// 연결 성공 확인
socket.on('connected', (data) => {
  console.log('✅ 서버 연결 성공:', data);
  // { message: "채팅 서버에 성공적으로 연결되었습니다.", userId: 1, nickname: "사용자1", connectedAt: "2024-01-01T..." }
});

// 연결 에러 처리
socket.on('connect_error', (error) => {
  console.error('❌ 연결 실패:', error.message);
  // 토큰 만료시 재로그인 처리
});

React Native

import io from 'socket.io-client';
import AsyncStorage from '@react-native-async-storage/async-storage';

const initSocket = async () => {
  const token = await AsyncStorage.getItem('accessToken');
  
  const socket = io('ws://localhost:3001/chat', {
    auth: { token }
  });
  
  return socket;
};

채팅 기능 구현

1. 채팅방 참가

// 채팅방 참가
const joinChatRoom = (chatRoomId) => {
  socket.emit('join_room', { chatRoomId });
};

// 참가 성공 응답
socket.on('joined_room', (data) => {
  console.log(`채팅방 ${data.chatRoomId} 참가 완료`);
  setCurrentChatRoom(data.chatRoomId);
});

// 다른 사용자 참가 알림
socket.on('user_joined', (data) => {
  console.log(`${data.nickname}님이 참가했습니다.`);
  showNotification(data.message);
});

2. 메시지 전송 및 수신 (주인님 요구사항)

// 메시지 전송
const sendMessage = (chatRoomId, content, mentionUserIds = []) => {
  socket.emit('message:send', {
    chatRoomId,
    type: 'text',
    content,
    mentionUserIds // @멘션할 사용자 ID 배열
  });
};

// 메시지 수신
socket.on('message:receive', (message) => {
  console.log('새 메시지:', message);
  /*
  message 구조:
  {
    id: 123,
    chatRoomId: 1,
    senderId: 2,
    type: 'text',
    content: '안녕하세요!',
    createdAt: '2024-01-01T12:00:00.000Z',
    sender: {
      id: 2,
      nickname: '사용자2',
      profileImg: 'https://...'
    },
    mentions: [
      {
        id: 1,
        mentionedUserId: 3,
        mentionedUser: {
          id: 3,
          nickname: '사용자3'
        }
      }
    ]
  }
  */
  
  // UI에 메시지 추가
  addMessageToChat(message);
});

// 멘션 알림 수신 (주인님 요구사항)
socket.on('mention:receive', (mention) => {
  console.log('멘션 알림:', mention);
  /*
  mention 구조:
  {
    messageId: 123,
    chatRoomId: 1,
    mentionedUserId: 3,
    mentionedUserNickname: '사용자3',
    senderNickname: '사용자2',
    content: '@사용자3 안녕하세요!',
    createdAt: '2024-01-01T12:00:00.000Z'
  }
  */
  
  // 푸시 알림 또는 UI 알림 표시
  showMentionNotification(mention);
});

3. 타이핑 상태

let typingTimer;

// 타이핑 시작
const handleTypingStart = (chatRoomId) => {
  socket.emit('typing:start', { chatRoomId });
  
  // 3초 후 자동으로 타이핑 중단
  clearTimeout(typingTimer);
  typingTimer = setTimeout(() => {
    socket.emit('typing:stop', { chatRoomId });
  }, 3000);
};

// 타이핑 중단
const handleTypingStop = (chatRoomId) => {
  clearTimeout(typingTimer);
  socket.emit('typing:stop', { chatRoomId });
};

// 다른 사용자 타이핑 상태 수신
socket.on('typing:status', (data) => {
  if (data.isTyping) {
    showTypingIndicator(`${data.nickname}님이 입력 중...`);
  } else {
    hideTypingIndicator(data.userId);
  }
});

4. 채팅방 나가기

const leaveChatRoom = (chatRoomId) => {
  socket.emit('leave_room', { chatRoomId });
};

socket.on('left_room', (data) => {
  console.log(`채팅방 ${data.chatRoomId}에서 나갔습니다.`);
  setCurrentChatRoom(null);
});

socket.on('user_left', (data) => {
  console.log(`${data.nickname}님이 나갔습니다.`);
  showNotification(data.message);
});

에러 처리

socket.on('error', (error) => {
  console.error('Socket 에러:', error);
  
  switch(error.type) {
    case 'message_send_failed':
      showErrorToast('메시지 전송에 실패했습니다.');
      break;
    default:
      showErrorToast(error.message);
  }
});

완전한 React 컴포넌트 예시

import React, { useState, useEffect, useRef } from 'react';
import io from 'socket.io-client';

const ChatRoom = ({ chatRoomId, accessToken }) => {
  const [socket, setSocket] = useState(null);
  const [messages, setMessages] = useState([]);
  const [inputMessage, setInputMessage] = useState('');
  const [typingUsers, setTypingUsers] = useState([]);
  const messagesEndRef = useRef(null);

  useEffect(() => {
    // Socket 연결
    const newSocket = io('ws://localhost:3001/chat', {
      auth: { token: accessToken }
    });

    newSocket.on('connected', (data) => {
      console.log('연결 완료:', data);
      // 채팅방 참가
      newSocket.emit('join_room', { chatRoomId });
    });

    // 메시지 수신
    newSocket.on('message:receive', (message) => {
      setMessages(prev => [...prev, message]);
      scrollToBottom();
    });

    // 멘션 알림
    newSocket.on('mention:receive', (mention) => {
      // 현재 사용자가 멘션된 경우에만 알림
      if (mention.mentionedUserId === getCurrentUserId()) {
        showNotification(`${mention.senderNickname}님이 회원님을 멘션했습니다.`);
      }
    });

    // 타이핑 상태
    newSocket.on('typing:status', (data) => {
      setTypingUsers(prev => {
        if (data.isTyping) {
          return [...prev.filter(u => u.userId !== data.userId), data];
        } else {
          return prev.filter(u => u.userId !== data.userId);
        }
      });
    });

    setSocket(newSocket);

    return () => {
      newSocket.disconnect();
    };
  }, [chatRoomId, accessToken]);

  const sendMessage = () => {
    if (inputMessage.trim() && socket) {
      // @멘션 파싱 (간단한 예시)
      const mentionRegex = /@(\w+)/g;
      const mentions = [];
      let match;
      while ((match = mentionRegex.exec(inputMessage)) !== null) {
        // 실제로는 사용자 닉네임을 ID로 변환하는 로직 필요
        const userId = getUserIdByNickname(match[1]);
        if (userId) mentions.push(userId);
      }

      socket.emit('message:send', {
        chatRoomId,
        type: 'text',
        content: inputMessage,
        mentionUserIds: mentions
      });

      setInputMessage('');
    }
  };

  const handleInputChange = (e) => {
    setInputMessage(e.target.value);
    
    // 타이핑 상태 전송
    if (socket) {
      socket.emit('typing:start', { chatRoomId });
    }
  };

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };

  return (
    <div className="chat-room">
      <div className="messages">
        {messages.map((message) => (
          <div key={message.id} className="message">
            <strong>{message.sender.nickname}: </strong>
            {message.content}
            <small>{new Date(message.createdAt).toLocaleTimeString()}</small>
          </div>
        ))}
        {typingUsers.length > 0 && (
          <div className="typing-indicator">
            {typingUsers.map(u => u.nickname).join(', ')}님이 입력 중...
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>
      
      <div className="input-area">
        <input
          type="text"
          value={inputMessage}
          onChange={handleInputChange}
          onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
          placeholder="메시지를 입력하세요... (@닉네임으로 멘션)"
        />
        <button onClick={sendMessage}>전송</button>
      </div>
    </div>
  );
};

export default ChatRoom;

그룹 채팅 특별 고려사항

1. 멤버 관리

// 그룹 채팅방에서는 여러 명이 동시에 메시지를 보낼 수 있음
// 메시지 순서 보장을 위해 서버에서 timestamp 사용

// 새 멤버 참가시
socket.on('user_joined', (data) => {
  // 그룹 채팅방 멤버 목록 업데이트
  updateChatRoomMembers(data.userId, 'add');
  showSystemMessage(`${data.nickname}님이 참가했습니다.`);
});

2. 멘션 기능

// 그룹 채팅에서 @all, @here 같은 특별 멘션도 지원 가능
const sendGroupMessage = (content) => {
  let mentionUserIds = [];
  
  if (content.includes('@all')) {
    // 모든 멤버 멘션
    mentionUserIds = getAllChatRoomMemberIds();
  } else {
    // 개별 멘션 파싱
    mentionUserIds = parseMentions(content);
  }
  
  socket.emit('message:send', {
    chatRoomId,
    type: 'text',
    content,
    mentionUserIds
  });
};

나중에 추가될 알림 기능 준비

// 알림 구독 (나중에 구현)
socket.emit('notification:subscribe');

// 팔로우 알림
socket.on('notification:follow', (data) => {
  showNotification(`${data.follower.nickname}님이 회원님을 팔로우했습니다.`);
});

// 좋아요 알림
socket.on('notification:like', (data) => {
  showNotification(`${data.liker.nickname}님이 회원님의 게시물을 좋아합니다.`);
});

// 댓글 알림
socket.on('notification:comment', (data) => {
  showNotification(`${data.commenter.nickname}님이 댓글을 남겼습니다: ${data.comment}`);
});