구현할 기능
서버는 죽은 클라이언트를 약 17분 뒤에야 인지하고, 중계 뒤에서 끊기면 영영 모른다.
클라이언트는 서버 ping 이 주석이라 스트림 생존을 관측할 수 없다.
양방향 하트비트와 연결 식별자로 양쪽 모두 연결 상태를 정확히 파악한다.
동작
connect 이벤트에서 connectionId 수신·보관, 재연결 시 갱신
- SSE 연결 중 + 포그라운드일 때 30초마다
connectionId 를 실어 하트비트 POST. 백그라운드에선 보내지 않고, 실패해도 재시도하지 않고 다음 주기에 보낸다
401 은 다른 API 와 같이 토큰 갱신 대상 (연결 30분이 토큰 15분보다 길어 연결 중 한 번은 만난다)
409 (NOTIFICATION-002) 응답 시 즉시 재연결 (서버가 이미 연결을 정리한 상태)
- 서버
heartbeat 이벤트가 60초 이상 결측되면 강제 재연결 (워치독). 임계값은 한 번 밀린 ping 을 끊김으로 오판하지 않도록 두 주기
- 재연결 전에 토큰이 만료됐으면 refresh 한 뒤 연다. 연결 중 만료는 그 연결을 끊지 않는다
워치독이 별도로 필요한 이유
하트비트 POST 는 "클라이언트가 서버에 요청을 보낼 수 있다"만 증명하고, "서버 → 클라이언트 스트림이 살아있다"는 증명하지 못한다. 서로 다른 연결이기 때문.
스트림만 끊기고 일반 요청은 정상인 경우, POST 는 계속 성공하므로 서버는 정상으로 판단하고 409 도 돌아오지 않는다. 이 상태는 클라이언트가 스트림 유입을 직접 관측해야만 감지되고, 새 번호로 POST 가 오면 서버가 옛 연결을 정리한다.
작업 상세 내용
참고
apps/web/src/hooks/useNotificationSSE.ts
apps/web/src/app/api/notifications/subscribe/route.ts
- SSE 명세 문서 (수동 관리, 클라이언트용 정본)
구현할 기능
서버는 죽은 클라이언트를 약 17분 뒤에야 인지하고, 중계 뒤에서 끊기면 영영 모른다.
클라이언트는 서버 ping 이 주석이라 스트림 생존을 관측할 수 없다.
양방향 하트비트와 연결 식별자로 양쪽 모두 연결 상태를 정확히 파악한다.
동작
connect이벤트에서connectionId수신·보관, 재연결 시 갱신connectionId를 실어 하트비트 POST. 백그라운드에선 보내지 않고, 실패해도 재시도하지 않고 다음 주기에 보낸다401은 다른 API 와 같이 토큰 갱신 대상 (연결 30분이 토큰 15분보다 길어 연결 중 한 번은 만난다)409(NOTIFICATION-002) 응답 시 즉시 재연결 (서버가 이미 연결을 정리한 상태)heartbeat이벤트가 60초 이상 결측되면 강제 재연결 (워치독). 임계값은 한 번 밀린 ping 을 끊김으로 오판하지 않도록 두 주기워치독이 별도로 필요한 이유
하트비트 POST 는 "클라이언트가 서버에 요청을 보낼 수 있다"만 증명하고, "서버 → 클라이언트 스트림이 살아있다"는 증명하지 못한다. 서로 다른 연결이기 때문.
스트림만 끊기고 일반 요청은 정상인 경우, POST 는 계속 성공하므로 서버는 정상으로 판단하고 409 도 돌아오지 않는다. 이 상태는 클라이언트가 스트림 유입을 직접 관측해야만 감지되고, 새 번호로 POST 가 오면 서버가 옛 연결을 정리한다.
작업 상세 내용
@piki/core에러 카탈로그에NOTIFICATION-002등록 (ERROR_CODE.NOTIFICATION_CONNECTION_NOT_FOUND)ENDPOINTS에 하트비트 엔드포인트 상수 추가postNotificationHeartbeat작성 (본문connectionId). 401 은clientApi인터셉터에 위임connect이벤트 핸들링 추가 :connectionId수신·보관, 재연결 시작 시 비움NOTIFICATION-002) 응답 시 즉시 재연결. 응답 지연 중 이미 재연결됐으면 무시기본 에러 처리를 타면 30초마다 토스트가 뜰 수 있음
참고
apps/web/src/hooks/useNotificationSSE.tsapps/web/src/app/api/notifications/subscribe/route.ts