-
Notifications
You must be signed in to change notification settings - Fork 0
Alarm Document
JeongHyeon Kim edited this page Dec 15, 2025
·
2 revisions
본 문서는 SSE(Server-Sent Events)를 통해 전달되는 알림 이벤트의 종류와 데이터 스펙을 정의합니다.
- 대상: 프론트엔드 개발자
- 목적: 알림 UI 구현 및 이벤트 분기 처리
-
전송 방식: SSE (
text/event-stream)
GET /api/v1/notifications/subscribe?accessToken={JWT_ACCESS_TOKEN}- Query Parameter로
accessToken전달 -
Authorization Header사용 불가 (EventSource 미지원)
const es = new EventSource(
`/api/v1/notifications/subscribe?accessToken=${accessToken}`
);
es.addEventListener("eventName", (event) => {
const data = JSON.parse(event.data);
// 이벤트 처리 로직
});
⚠️ 주의: 서버에서event.name이 지정된 이벤트는
반드시addEventListener("이벤트명")으로 수신해야 합니다.
| 이벤트명 | 설명 | 필수 여부 |
|---|---|---|
connect |
SSE 연결 확인용 이벤트 | 필수 |
notification |
사용자 알림 이벤트 | 필수 |
heartbeat |
연결 유지용 ping (예정) | 선택 |
- SSE 연결 직후 서버에서 1회 전송
- 연결 성공 여부 확인 용도
- UI 표시 불필요 (로그 용도)
event: connect
data: Connected
es.addEventListener("connect", (event) => {
console.log("SSE 연결 확인:", event.data);
});- 실제 사용자 알림 이벤트
- 팔로우, 모임 생성/참여/취소 등 공통 이벤트
{
"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 | 의미 | 프론트 처리 예시 |
|---|---|---|
FOLLOW |
팔로우 알림 | 알림 뱃지 증가, 프로필 이동 |
ENTER |
모임 참여 알림 | 모임 참여자 목록 업데이트 |
EXIT |
모임 퇴장 알림 | 모임 참여자 목록 업데이트 |
CREATE |
모임 생성 알림 | 새 모임 알림 표시 |
CANCLE |
모임 취소 알림 | 모임 취소 안내 표시 |
{
"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"
}{
"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"
}{
"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"
}{
"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"
}{
"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);
}
});es.onerror = (error) => {
console.log("SSE 오류 발생, 연결 종료");
// 토큰 만료 확인
if (error.status === 401) {
refreshAccessToken().then(() => {
// 재연결
reconnectSSE();
});
} else {
es.close();
}
};- Access Token 만료 (401)
- 서버 재시작 (503)
- 네트워크 단절
- ❌
onmessage사용 불가 - ✅ 반드시
addEventListener("notification")사용
-
accessTokenURL 노출 주의 - HTTPS 환경 필수
- 토큰 만료 시 자동 재연결 로직 구현 필요
-
actorId,actorNickname,actorProfileImage: 시스템 알림의 경우 null 가능 -
relatedId,relatedType: 팔로우 알림의 경우 null 가능
| 이벤트명 | 설명 |
|---|---|
heartbeat |
연결 유지용 ping |
system |
시스템 공지 |
reconnect |
재연결 안내 |
{
"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클릭 시 정상 이동 - 토큰 만료 시 재연결 로직 동작
- SSE 알림은
notification이벤트로 전달됩니다. - 프론트에서는
type기준으로 UI 분기 처리합니다. - Actor 정보(닉네임, 프로필 이미지)를 활용하여 풍부한 알림 UI를 구성할 수 있습니다.
- Related 정보(ID, Type, URL)를 활용하여 알림 클릭 시 적절한 페이지로 이동합니다.
- 반드시 로그인 → SSE 연결 → 이벤트 발생 순서를 지켜야 합니다.