personal project

Oh! Sori

친구 · DM · 음성 통화 · 화면 공유를 제공하는 디스코드형 음성채팅 커뮤니티

2025.05 – 2025.07GitHub ↗
DM 채팅 (다크 테마)
DM 채팅 (다크 테마)
1인
개인 프로젝트
110+
커밋
P2P
WebRTC 음성통화
Docker
Compose 배포

01개요

디스코드처럼 친구를 추가하고 DM과 음성 통화, 화면 공유로 소통하는 커뮤니티 서비스입니다. 프론트엔드(Next.js)와 백엔드(Express + Socket.IO)를 모두 TypeScript로 직접 구현하고 Docker Compose로 배포했습니다.

특히 WebRTC 1:1 음성 통화를 실제 서비스 수준으로 다듬는 데 집중해, 새로고침 후 통화 복구, 통화 중 화면 공유, 발화 감지, NAT 환경 대응까지 구현했습니다.

02기술 스택

Frontend
Next.js 15React 19TypeScriptRedux ToolkitTailwind CSS
Backend
Node.jsExpressTypeScriptSocket.IOJWT (httpOnly 쿠키)Multer
Data
MongoDB · MongooseRedis (ioredis)
Realtime
WebRTCSTUN · TURNScreen Capture APIWeb Audio API
Infra / Auth
Docker ComposeGoogle · Kakao · Naver OAuth

03아키텍처

Next.js Client
Redux · Socket.IO client
Express + Socket.IO
REST · 채팅 · 통화 시그널링
MongoDB
유저 · 친구 · DM
Redis
접속 상태 · 통화 세션
상대 Client
WebRTC P2P (STUN/TURN)

음성·화면은 클라이언트끼리 WebRTC로 직접 연결하고, 서버는 시그널링과 상태 관리만 담당 · client / server / mongo 컨테이너를 Docker Compose로 구성

04주요 기능

계정 · 친구

  • Google·Kakao·Naver 소셜 로그인과 일반 회원가입, JWT를 httpOnly 쿠키로 발급
  • 닉네임#태그 방식 친구 추가·수락·거절·삭제, 받은 요청 수 배지
  • 온라인·자리 비움·방해 금지·오프라인 상태를 Redis에 저장하고 소켓으로 실시간 반영

DM 채팅

  • Socket.IO 방 단위 1:1 실시간 메시지, 읽음 처리·안 읽은 메시지 수
  • 파일 첨부(Multer), 메시지 삭제, 무한 스크롤로 이전 메시지 20개씩 로드

음성 통화 · 화면 공유

  • WebRTC P2P 음성 통화, Socket.IO로 offer/answer/ICE 시그널링
  • 수신 알림 토스트·링백음, 통화 중 화면 공유 시작/종료
  • Web Audio API로 말하는 사람을 감지해 상대 화면에 발화 표시

UI

  • 디스코드형 3단 레이아웃(서버 바 · DM 목록 · 본문)
  • 다크/라이트 테마, 모바일 사이드바
시작 화면
시작 화면
소셜 로그인
소셜 로그인
친구 목록 · 상태 표시
친구 목록 · 상태 표시
DM 채팅 (라이트 테마)
DM 채팅 (라이트 테마)

* 스크린샷은 로컬에서 데모 데이터로 재현한 화면입니다.

05트러블슈팅

새로고침하면 진행 중인 통화가 끊김

문제
통화 중 한쪽이 새로고침하거나 잠시 이탈하면 통화 상태가 사라지고 다시 걸어야 함
원인
통화 상태가 클라이언트 메모리와 소켓 연결에만 존재
해결
Redis에 통화방 세션(caller·callee·시작 시각·각자 종료 여부)을 저장하고, 재접속 시 세션을 조회해 재연결(call:reconn). 한쪽만 종료된 경우 3분 유예 타이머 후 세션 정리

통화 화면이 준비되기 전에 offer가 도착

문제
수락 직후 통화 컴포넌트가 마운트되기 전에 offer·ICE candidate가 먼저 와서 유실
원인
시그널링 수신 시점과 WebRTC 연결을 처리할 컴포넌트의 생성 시점이 다름
해결
offer와 candidate를 sessionStorage에 임시 보관하고, 통화 컴포넌트가 준비되면 대기(waitForOffer) 후 꺼내 처리

통화 중 화면 공유 시작·종료가 상대에게 반영되지 않음

문제
연결을 새로 맺지 않으면 공유 화면이 안 보이고, 종료해도 상대 화면에 마지막 프레임이 남음
원인
기존 연결에 트랙을 추가하려면 재협상이 필요하고, 트랙을 빼면 m-line 구성이 바뀜
해결
트랙 추가 시 renegotiate offer/answer로 재협상, 종료 시 replaceTrack(null)로 같은 mid를 유지해 상대가 즉시 종료를 감지

링백음 · 수신음이 재생되지 않음

문제
통화 알림음이 브라우저에 따라 재생되지 않음
원인
사용자 상호작용 전 오디오 재생을 막는 자동 재생 정책
해결
첫 클릭 시 무음 오디오를 한 번 재생해 오디오를 언락

일부 네트워크에서 P2P 연결 실패

문제
같은 코드로도 사용자 네트워크 환경에 따라 음성이 연결되지 않음
원인
대칭형 NAT 등에서는 STUN만으로 직접 연결 불가
해결
STUN과 함께 자체 TURN 서버를 구성해 직접 연결이 안 되면 릴레이로 연결

06회고

  • TURN 계정이 클라이언트 코드에 고정 — 시간 제한이 있는 임시 자격 증명(TURN REST API) 방식으로 바꿀 필요
  • Redis에서 KEYS로 통화방을 찾는 부분 — 사용자별 인덱스 키나 SCAN으로 교체 필요
  • 1:1 통화 중심 설계 — 그룹 통화로 확장하려면 SFU 구조 검토
Gunmo Lee