docs(user-creator-chat): 채팅 푸시 deep link 계약을 기록한다

This commit is contained in:
2026-06-19 03:57:56 +09:00
parent 8b80ca6344
commit 5d18f478ab
2 changed files with 42 additions and 40 deletions

View File

@@ -21,7 +21,7 @@
- WebSocket 전환과 별도로 `spring.jpa.open-in-view=false` 적용 가능성을 점검하고, 트랜잭션 밖 lazy loading에 의존하는 API를 먼저 식별한다.
- 같은 `roomId` 채팅방 화면에 상대방이 접속 중이면 새 메시지를 WebSocket으로 전달하고 푸시는 발송하지 않는다.
- 상대방이 해당 `roomId` 채팅방 화면에 접속 중이 아니면 새 메시지 저장 후 푸시를 발송한다.
- 푸시 payload에는 앱이 해당 채팅방으로 이동할 수 있는 `roomId`와 채팅 타입 식별 정보를 포함한다.
- 푸시 payload에는 앱이 해당 채팅방으로 이동할 수 있는 `deep_link` 포함한다.
- 기존 방 생성, 방 열기, 과거 메시지 조회, 음성 메시지 업로드 API는 유지한다.
- 텍스트 메시지는 WebSocket으로 전송하고, 서버는 저장 결과를 sender에게 ack로 돌려준다.
@@ -137,14 +137,11 @@
#### Requirements
- 상대방이 해당 채팅방 화면에 있지 않으면 기존 FCM/APNs 푸시 발송 흐름을 사용한다.
- 푸시 category는 기존 `PushNotificationCategory.MESSAGE`를 사용한다.
- 푸시 payload에는 최소 다음 값을 포함한다.
- `room_id`: 채팅방 ID
- `message_id`: 새 메시지 ID
- `chat_type`: `USER_CREATOR`
- `deep_link`: 앱이 채팅방으로 이동할 수 있는 값
- 기존 `FcmEvent.roomId`, `FcmEvent.messageId`, `FcmService.putData("room_id", ...)`, `putData("message_id", ...)` 흐름은 유지한다.
- `chat_type` 또는 동등한 식별자가 현재 FCM payload에 없으면 추가한다.
- 앱은 푸시 터치 시 `room_id``chat_type`을 기준으로 `GET /api/v2/user-creator-chat/rooms/{roomId}/open` 호출 후 WebSocket 연결을 시작한다.
- 푸시 payload에는 `deep_link` 포함한다.
- 운영: `voiceon://chat/{roomId}`
- 개발/테스트: `voiceon-test://chat/{roomId}`
- v2 채팅 푸시에서는 기존 `room_id`, `message_id`, `chat_type` data payload를 사용하지 않는다.
- 앱은 푸시 터치 시 `deep_link`에서 `{roomId}`를 해석해 `GET /api/v2/user-creator-chat/rooms/{roomId}/open` 호출 후 WebSocket 연결을 시작한다.
#### Edge Cases
- 푸시 발송 대상 push token이 없으면 메시지 저장은 성공하고 `pushSent == false` 의미의 내부 결과를 남긴다.
@@ -191,7 +188,7 @@
- 재연결 성공 후 `JOIN_ROOM`을 다시 보내고, 필요하면 REST `messages` API로 누락 메시지를 동기화한다.
- 기존 SSE retry/backoff 코드는 WebSocket 재연결 정책으로 대체하고, 채팅방 화면 밖에서는 재연결하지 않는다.
- 앱은 heartbeat로 `PING`을 주기적으로 보내고 `PONG`을 수신해 연결 상태를 판단한다.
- 푸시 알림을 터치하면 payload의 `chat_type == "USER_CREATOR"``room_id`를 확인해 해당 채팅방 화면으로 이동한다.
- 푸시 알림을 터치하면 payload의 `deep_link`를 확인해 해당 채팅방 화면으로 이동한다.
- 푸시로 채팅방에 진입한 뒤에도 일반 진입과 동일하게 `openRoom` 호출 후 WebSocket 연결/`JOIN_ROOM`을 수행한다.
- 클라이언트는 제거된 SSE endpoint와 `events/disconnect` endpoint를 더 이상 호출하지 않는다.
- 클라이언트는 기존 `POST /api/v2/user-creator-chat/rooms/{roomId}/messages/text` 응답의 `deliveredRealtime`/`pushSent`를 텍스트 전송 UI 판단에 사용하지 않는다. 텍스트 전송 성공 여부는 WebSocket `SEND_ACK`/`ERROR`/timeout으로 판단한다.
@@ -204,14 +201,14 @@
- 텍스트 전송 변경: `POST /api/v2/user-creator-chat/rooms/{roomId}/messages/text` 호출을 제거하고, `SEND_TEXT` 메시지와 `SEND_ACK` 매칭 방식으로 pending/성공/실패 상태를 관리한다.
- 음성 전송 유지: `POST /api/v2/user-creator-chat/rooms/{roomId}/messages/voice` multipart 호출은 유지하되, 음성 전송 후 상대방 실시간 수신 여부는 서버 정책에 따른다.
- 토큰 갱신 처리 변경: access token refresh 시 기존 WebSocket을 close하고 새 token의 `Authorization` 헤더로 다시 연결한 뒤 `JOIN_ROOM`을 다시 보낸다.
- 푸시 이동 처리 확인: `chat_type == "USER_CREATOR"``room_id`를 기준으로 채팅방에 진입하고, 진입 후 `openRoom` 호출과 WebSocket `JOIN_ROOM`을 일반 진입과 동일하게 수행한다.
- 푸시 이동 처리 확인: `deep_link``voiceon://chat/{roomId}` 또는 `voiceon-test://chat/{roomId}`를 기준으로 채팅방에 진입하고, 진입 후 `openRoom` 호출과 WebSocket `JOIN_ROOM`을 일반 진입과 동일하게 수행한다.
#### Edge Cases
- WebSocket 연결은 성공했지만 `JOIN_ROOM`이 실패하면 채팅방 화면에 오류를 표시하고 텍스트 전송을 막는다.
- `SEND_TEXT` 후 일정 시간 안에 `SEND_ACK`가 오지 않으면 메시지를 전송 실패 상태로 표시하고 재시도 UI를 제공한다.
- 재연결 전 pending 메시지를 자동 재전송할 경우 같은 `requestId`를 재사용해 중복 표시를 방지한다.
- 앱이 다른 채팅방으로 이동하면 기존 방 WebSocket session을 종료하고 새 방으로 다시 연결한다.
- 푸시 payload에 `room_id`가 없거나 숫자로 파싱할 수 없으면 채팅방 직접 이동을 하지 않고 앱 기본 알림 처리 흐름을 따른다.
- 푸시 payload에 `deep_link`가 없거나 `voiceon://chat/{roomId}` / `voiceon-test://chat/{roomId}` 형식에서 `roomId`를 해석할 수 없으면 채팅방 직접 이동을 하지 않고 앱 기본 알림 처리 흐름을 따른다.
### Feature G. OSIV 비활성화 사전 점검
@@ -240,7 +237,7 @@
## 8. UX / UI Expectations
- 채팅방 화면 진입 시 REST `openRoom` 응답으로 초기 화면을 그리고, WebSocket `JOINED` 이후 새 메시지를 실시간으로 append한다.
- 채팅방 화면에 있는 동안 같은 방의 메시지 푸시가 나타나지 않아야 한다.
- 채팅방 화면 밖에서 푸시를 터치하면 해당 `roomId` 채팅방으로 이동해야 한다.
- 채팅방 화면 밖에서 푸시를 터치하면 `deep_link` `{roomId}`에 해당하는 채팅방으로 이동해야 한다.
- WebSocket 재연결 중 사용자가 보낸 메시지는 앱에서 전송 실패 또는 재시도 상태로 표시할 수 있어야 한다.
- 앱 백그라운드 진입 또는 화면 이탈 시 `LEAVE_ROOM`을 보내고 WebSocket을 close한다.
- 앱은 기존 SSE 연결 코드와 `events/disconnect` 호출 코드를 제거한다.