Skip to content

Alarm Document

JeongHyeon Kim edited this page Dec 15, 2025 · 2 revisions

📘 SSE 이벤트 스펙 (Notification Events)

1. 개요

본 문서는 SSE(Server-Sent Events)를 통해 전달되는 알림 이벤트의 종류와 데이터 스펙을 정의합니다.

  • 대상: 프론트엔드 개발자
  • 목적: 알림 UI 구현 및 이벤트 분기 처리
  • 전송 방식: SSE (text/event-stream)

2. SSE 기본 정보

📡 구독 Endpoint

GET /api/v1/notifications/subscribe?accessToken={JWT_ACCESS_TOKEN}

🔐 인증 방식

  • Query Parameter로 accessToken 전달
  • Authorization Header 사용 불가 (EventSource 미지원)

3. 프론트엔드 수신 기본 패턴

const es = new EventSource(
  `/api/v1/notifications/subscribe?accessToken=${accessToken}`
);

es.addEventListener("eventName", (event) => {
  const data = JSON.parse(event.data);
  // 이벤트 처리 로직
});

⚠️ 주의: 서버에서 event.name이 지정된 이벤트는
반드시 addEventListener("이벤트명")으로 수신해야 합니다.


4. 이벤트 목록

이벤트명 설명 필수 여부
connect SSE 연결 확인용 이벤트 필수
notification 사용자 알림 이벤트 필수
heartbeat 연결 유지용 ping (예정) 선택

5. 이벤트 상세 스펙

5.1 connect 이벤트

📝 설명

  • SSE 연결 직후 서버에서 1회 전송
  • 연결 성공 여부 확인 용도
  • UI 표시 불필요 (로그 용도)

이벤트 형식

event: connect
data: Connected

프론트 처리 예시

es.addEventListener("connect", (event) => {
  console.log("SSE 연결 확인:", event.data);
});

5.2 notification 이벤트

📝 설명

  • 실제 사용자 알림 이벤트
  • 팔로우, 모임 생성/참여/취소 등 공통 이벤트

데이터 구조 (공통)

{
  "id": 1,
  "receiverId": 100,
  "actorId": 102,
  "actorNickname": "user0",
  "actorProfileImage": "https://example.com/profile/user0.jpg",
  "type": "FOLLOW",
  "message": "user0님이 회원님을 팔로우하기 시작했습니다.",
  "isRead": false,
  "relatedId": 500,
  "relatedType": "FOLLOW",
  "redirectUrl": "/profile/102",
  "createdAt": "2025-12-15T22:35:21"
}

필드 설명

필드명 타입 필수 여부 설명
id number 필수 알림 고유 ID
receiverId number 필수 알림 수신자 ID
actorId number 선택 알림 발생시킨 사용자 ID
actorNickname string 선택 알림 발생시킨 사용자 닉네임
actorProfileImage string 선택 알림 발생시킨 사용자 프로필 이미지
type string 필수 알림 타입
message string 필수 사용자에게 표시할 메시지
isRead boolean 필수 읽음 여부
relatedId number 선택 연관 리소스 ID (게시글 ID 등)
relatedType string 선택 연관 리소스 타입
redirectUrl string 선택 클릭 시 이동할 경로
createdAt string (ISO-8601) 필수 알림 생성 시각

type 값 정의

type 의미 프론트 처리 예시
FOLLOW 팔로우 알림 알림 뱃지 증가, 프로필 이동
ENTER 모임 참여 알림 모임 참여자 목록 업데이트
EXIT 모임 퇴장 알림 모임 참여자 목록 업데이트
CREATE 모임 생성 알림 새 모임 알림 표시
CANCLE 모임 취소 알림 모임 취소 안내 표시

알림 타입별 데이터 예시

FOLLOW (팔로우 알림)
{
  "id": 1,
  "receiverId": 100,
  "actorId": 102,
  "actorNickname": "user0",
  "actorProfileImage": "https://example.com/profile/user0.jpg",
  "type": "FOLLOW",
  "message": "user0님이 회원님을 팔로우하기 시작했습니다.",
  "isRead": false,
  "relatedId": null,
  "relatedType": "FOLLOW",
  "redirectUrl": "/profile/102",
  "createdAt": "2025-12-15T22:35:21"
}
ENTER (모임 참여 알림)
{
  "id": 2,
  "receiverId": 100,
  "actorId": 105,
  "actorNickname": "participant1",
  "actorProfileImage": "https://example.com/profile/participant1.jpg",
  "type": "ENTER",
  "message": "participant1님이 모임에 참여하셨습니다.",
  "isRead": false,
  "relatedId": 500,
  "relatedType": "POST",
  "redirectUrl": "/post/500",
  "createdAt": "2025-12-15T22:40:15"
}
EXIT (모임 퇴장 알림)
{
  "id": 3,
  "receiverId": 100,
  "actorId": 106,
  "actorNickname": "leaver1",
  "actorProfileImage": "https://example.com/profile/leaver1.jpg",
  "type": "EXIT",
  "message": "leaver1님이 모임에서 퇴장하셨습니다.",
  "isRead": false,
  "relatedId": 350,
  "relatedType": "POST",
  "redirectUrl": "/post/500",
  "createdAt": "2025-12-15T23:10:30"
}
CREATE (모임 생성 알림)
{
  "id": 4,
  "receiverId": 100,
  "actorId": 110,
  "actorNickname": "creator1",
  "actorProfileImage": "https://example.com/profile/creator1.jpg",
  "type": "CREATE",
  "message": "creator1님이 모임을 생성하셨습니다.",
  "isRead": false,
  "relatedId": 520,
  "relatedType": "POST",
  "redirectUrl": "/post/520",
  "createdAt": "2025-12-15T23:20:45"
}
CANCLE (모임 취소 알림)
{
  "id": 5,
  "receiverId": 100,
  "actorId": 110,
  "actorNickname": "canceler1",
  "actorProfileImage": "https://example.com/profile/canceler1.jpg",
  "type": "CANCLE",
  "message": "canceler1님이 모임을 취소하셨습니다.",
  "isRead": false,
  "relatedId": 520,
  "relatedType": "POST",
  "redirectUrl": "/post/520",
  "createdAt": "2025-12-15T23:30:00"
}

프론트 처리 예시

es.addEventListener("notification", (event) => {
  const data = JSON.parse(event.data);
  
  // 공통 처리: 알림 뱃지 업데이트
  updateNotificationBadge();
  
  // 타입별 분기 처리
  switch (data.type) {
    case "FOLLOW":
      showToast({
        message: data.message,
        profileImage: data.actorProfileImage,
        onClick: () => navigateTo(data.redirectUrl)
      });
      break;
      
    case "ENTER":
      showToast({
        message: data.message,
        type: "info"
      });
      // 모임 참여자 목록 실시간 업데이트
      if (currentPostId === data.relatedId) {
        refreshParticipantList(data.relatedId);
      }
      break;
      
    case "EXIT":
      showToast({
        message: data.message,
        type: "warning"
      });
      // 모임 참여자 목록 실시간 업데이트
      if (currentPostId === data.relatedId) {
        refreshParticipantList(data.relatedId);
      }
      break;
      
    case "CREATE":
      showToast({
        message: data.message,
        type: "success"
      });
      // 새로운 모임 목록 추가
      addNewPostToList(data.relatedId);
      break;
      
    case "CANCLE":
      showToast({
        message: data.message,
        type: "error"
      });
      // 취소된 모임 처리
      if (currentPostId === data.relatedId) {
        showCancellationDialog();
      }
      removePostFromList(data.relatedId);
      break;
      
    default:
      console.log("알 수 없는 알림 타입:", data);
  }
});

6. 에러 및 종료 처리

SSE 에러 처리

es.onerror = (error) => {
  console.log("SSE 오류 발생, 연결 종료");
  
  // 토큰 만료 확인
  if (error.status === 401) {
    refreshAccessToken().then(() => {
      // 재연결
      reconnectSSE();
    });
  } else {
    es.close();
  }
};

주요 원인

  • Access Token 만료 (401)
  • 서버 재시작 (503)
  • 네트워크 단절

7. 주의사항

⚠️ 이벤트 수신 방식

  • onmessage 사용 불가
  • ✅ 반드시 addEventListener("notification") 사용

🔒 보안

  • accessToken URL 노출 주의
  • HTTPS 환경 필수
  • 토큰 만료 시 자동 재연결 로직 구현 필요

📌 Null 체크 필수 필드

  • actorId, actorNickname, actorProfileImage: 시스템 알림의 경우 null 가능
  • relatedId, relatedType: 팔로우 알림의 경우 null 가능

8. 확장 예정 이벤트 (Future)

이벤트명 설명
heartbeat 연결 유지용 ping
system 시스템 공지
reconnect 재연결 안내

9. 테스트 체크리스트

TEST ENDPOINT : GET /api/v1/notifications/test

TEST Response

{
  "id": 2, //로그인 유저 id
  "receiverId": 2, //로그인 유저 id
  "actorId": 2, //로그인 유저 id
  "actorNickname": "미니", //로그인 유저 닉네임
  "actorProfileImage": "https://we-go-bucket.s3.ap-northeast-2.amazonaws.com/20251215083204_24ff6cae-f4a2-4e68-8f7a-fa4a087488b3_440x240.webp", //로그인 유저 프로필 이미지
  "type": "TEST",
  "message": "테스트 알림 응답",
  "isRead": false,
  "relatedId": null,
  "relatedType": "TEST",
  "redirectUrl": "https://api.wego.monster/swagger-ui/index.html", //테스트 URL (운영 Swagger)
  "createdAt": "2025-12-15T23:28:52.9684693"
}
  • 로그인 후 accessToken 확보
  • SSE subscribe 성공 (connect 이벤트 수신)
  • 팔로우 발생 시 FOLLOW 타입 알림 수신 및 UI 업데이트
  • 모임 참여 시 ENTER 타입 알림 수신 및 참여자 목록 업데이트
  • 모임 퇴장 시 EXIT 타입 알림 수신 및 참여자 목록 업데이트
  • 모임 생성 시 CREATE 타입 알림 수신 및 목록 추가
  • 모임 취소 시 CANCLE 타입 알림 수신 및 취소 처리
  • actorProfileImage 표시 정상 동작
  • redirectUrl 클릭 시 정상 이동
  • 토큰 만료 시 재연결 로직 동작

10. 요약

  • SSE 알림은 notification 이벤트로 전달됩니다.
  • 프론트에서는 type 기준으로 UI 분기 처리합니다.
  • Actor 정보(닉네임, 프로필 이미지)를 활용하여 풍부한 알림 UI를 구성할 수 있습니다.
  • Related 정보(ID, Type, URL)를 활용하여 알림 클릭 시 적절한 페이지로 이동합니다.
  • 반드시 로그인 → SSE 연결 → 이벤트 발생 순서를 지켜야 합니다.

📚 참고 자료

Clone this wiki locally