From dacd3a67a14f70af450b49586d7f7e3af79ff593 Mon Sep 17 00:00:00 2001 From: klaus Date: Fri, 19 Jun 2026 05:03:37 +0900 Subject: [PATCH] =?UTF-8?q?docs(dm):=20DM=20push=20deep=5Flink=20=EA=B3=84?= =?UTF-8?q?=EC=95=BD=EC=9D=84=20=EA=B8=B0=EB=A1=9D=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/20260610_DM_채팅화면/plan-task.md | 44 +++++++++++++++++++++++--- docs/20260610_DM_채팅화면/prd.md | 9 ++++-- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/docs/20260610_DM_채팅화면/plan-task.md b/docs/20260610_DM_채팅화면/plan-task.md index 1daa442c..ffe7b2a5 100644 --- a/docs/20260610_DM_채팅화면/plan-task.md +++ b/docs/20260610_DM_채팅화면/plan-task.md @@ -73,11 +73,11 @@ - Modify: `app/src/main/AndroidManifest.xml` - `DmChatRoomActivity`를 등록한다. - Modify: `app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt` - - 푸시 payload의 `chat_type`, `room_id`를 `DeepLinkActivity`로 전달한다. + - 푸시 payload의 `deep_link`를 `DeepLinkActivity`로 전달한다. - Modify: `app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt` - - `chat_type == "USER_CREATOR"`와 `room_id` 기준으로 DM 채팅방 진입 intent를 만든다. + - `${URISCHEME}://chat/{roomId}` deep link 기준으로 DM 채팅방 진입 intent를 만든다. - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt` - - 로그인/메인 진입 후 전달된 DM 푸시 extras를 DM 채팅방 진입으로 연결한다. + - 로그인/메인 진입 후 전달된 DM deep link를 DM 채팅방 진입으로 연결한다. - Modify: `docs/agent-guides/build-test-style.md` - 신규 DM 채팅 테스트 단일 실행 예시를 추가한다. - Test Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatMapperTest.kt` @@ -755,6 +755,8 @@ ### Phase 12: 푸시 진입과 제거 endpoint 회귀 검증 +> 2026-06-19 추가 계약 변경: DM 채팅 FCM payload는 `chat_type`과 `room_id`를 보내지 않고, `${URISCHEME}://chat/{roomId}` 형식의 `deep_link`만 전달한다. Task 12.1~12.2는 이전 payload 계약 기준 완료 이력으로 보존하고, 새 계약 반영은 Task 12.5에서 진행한다. + - [x] **Task 12.1: FCM payload에 chat_type 전달 추가** - Files: - Modify: `app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt` @@ -826,6 +828,37 @@ - 2026-06-18: Phase 12 코드 리뷰 및 통합 검증으로 FCM `chat_type` 전달, foreground/LiveRoom foreground/Splash/Login 이후 USER_CREATOR DM 라우팅, 제거 endpoint 회귀 방지, 음성 DTO 보존 범위를 재검토했다. deepLink payload/query의 `chat_type` 누락 blocker 2건은 위 회귀 테스트로 고정 후 수정했다. 재검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRemovedEndpointSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. 최초 샌드박스 Gradle 실행은 `~/.gradle` wrapper lock 파일 접근 제한으로 실패해 승인된 Gradle 실행으로 재검증했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning만 출력됐다. - 2026-06-19: Phase 12 최신 코드 리뷰로 FCM deepLink/non-deepLink payload의 `chat_type`/`room_id` 보존, `DeepLinkActivity` foreground USER_CREATOR DM 선분기, URL query `chat_type` 보존, `MainV2Activity` Splash/Login 이후 USER_CREATOR DM 라우팅, 제거 endpoint 문자열 회귀 방지, 음성 DTO 보존 범위를 재확인했다. 추가 blocking issue는 발견하지 못했다. 재검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRemovedEndpointSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. 최초 샌드박스 Gradle 실행은 `~/.gradle` wrapper lock 파일 접근 제한으로 실패해 승인된 Gradle 실행으로 재검증했다. Gradle deprecation warning은 출력됐지만 실패는 없었다. +- [x] **Task 12.5: FCM deep_link 단독 payload로 DM 채팅방 라우팅 변경** + - Files: + - Modify: `app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt` + - Modify: `app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt` + - Modify: `app/src/main/java/kr/co/vividnext/sodalive/main/MainActivity.kt` + - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt` + - Test: `app/src/test/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingServiceSourceTest.kt` + - Test: `app/src/test/java/kr/co/vividnext/sodalive/main/DeepLinkActivitySourceTest.kt` + - Test: `app/src/test/java/kr/co/vividnext/sodalive/main/MainActivitySourceTest.kt` + - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt` + - 작업: + - 서버 FCM payload는 `chat_type`과 `room_id`를 보내지 않고 `deep_link`만 보낸다는 최신 계약을 반영한다. + - DM 채팅방 푸시 deep link 형식은 `${URISCHEME}://chat/{roomId}`로 정의한다. + - `SodaFirebaseMessagingService`는 `messageData["deep_link"]`를 notification intent extras 또는 URL deep link 경로로 보존한다. + - `DeepLinkActivity`는 `chat_type`/`room_id` 없이 deep link path `/chat/{roomId}`에서 `roomId`를 파싱해 `DmChatRoomActivity.newIntentByRoomId(context, roomId)`로 이동한다. + - LiveRoom foreground confirm 이후 legacy `MainActivity`로 전달되는 deep link도 같은 `/chat/{roomId}` 규칙으로 DM 채팅방에 연결한다. + - Splash/Login 이후 `MainV2Activity`로 전달되는 deep link도 같은 `/chat/{roomId}` 규칙으로 DM 채팅방에 연결한다. + - `roomId`가 없거나 Long으로 변환할 수 없으면 DM 채팅방을 열지 않고 기존 fallback 흐름을 따른다. + - `chat_type` 기반 DM 라우팅은 배포 전 폐기된 계약이므로 호환 경로로 남기지 않고 제거한다. + - 검증: + - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1` + - Expected: `deep_link=${URISCHEME}://chat/{roomId}` 단독 payload가 `chat_type`/`room_id` 없이 DM 채팅방으로 라우팅되고, invalid room fallback과 기존 deep link 흐름 유지 테스트가 PASS. + - 검증 기록: + - 2026-06-19: `SodaFirebaseMessagingServiceSourceTest`에 `deep_link` 단독 payload가 `ACTION_VIEW` data와 `deep_link` extra로 보존되는지 확인하는 source test를 추가했다. `DeepLinkActivitySourceTest`와 `MainV2ActivitySourceTest`에는 `${URISCHEME}://chat/{roomId}` path가 `chat_type`/`room_id` 없이 `room_id`, `deep_link_value=chat`, `deep_link_sub5`로 정규화되고 기존 live room fallback보다 먼저 `DmChatRoomActivity.newIntentByRoomId()`로 라우팅되는지 확인하는 source test를 추가했다. 수정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1` 실행 결과 `DeepLinkActivitySourceTest.kt:52`, `MainV2ActivitySourceTest.kt:29` assertion failure로 RED를 확인했다. + - 2026-06-19: `DeepLinkActivity.applyPathDeepLink()`와 `MainV2Activity.applyPathDeepLink()`에 `chat` path 매핑을 추가해 `room_id`, `deep_link_value=chat`, `deep_link_sub5`를 채우도록 변경했다. 기존 `chat_type=USER_CREATOR` 호환 경로는 유지하면서 `deep_link_value=chat`도 DM 채팅방으로 라우팅하도록 `isDmChatDeepLink()`를 추가했다. 이후 같은 source test 명령 재실행 결과 PASS를 확인했다. + - 2026-06-19: Task 12.5 최종 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning만 출력됐다. `adb devices` 결과 연결된 Android 기기가 없어 실제 앱에서 푸시 터치 또는 deep link launch 수동 확인은 수행하지 못했다. + - 2026-06-19: 리뷰 게이트에서 LiveRoom foreground 상태의 `DeepLinkActivity`가 `/chat/{roomId}`를 `ACTION_LIVE_ROOM_DEEPLINK_CONFIRM` broadcast로 먼저 넘길 수 있고, 해당 confirm 후 진입하는 legacy `MainActivity`에도 `chat` path DM 라우팅이 누락됐다는 blocker를 확인했다. `DeepLinkActivitySourceTest`와 신규 `MainActivitySourceTest`에 재현 테스트를 추가했고, 수정 전 `DeepLinkActivitySourceTest.kt:49`, `MainActivitySourceTest.kt:17` assertion failure로 RED를 확인했다. 이후 `DeepLinkActivity` foreground 선분기 조건을 `isDmChatDeepLink(deepLinkExtras)`로 변경하고, `MainActivity`에 `chat` path 정규화와 `DmChatRoomActivity.newIntentByRoomId()` DM 라우팅을 추가했다. 재실행 결과 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.main.MainActivitySourceTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.main.MainActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning만 출력됐다. + - 2026-06-19: 코드 품질 재리뷰에서 `deep_link`와 기존 `chat_type=USER_CREATOR`/`room_id` sidecar가 함께 전달되는 호환 payload의 경우 `MainV2Activity`와 `MainActivity`가 deep link URL 파싱 결과를 원본 bundle 대신 사용해 sidecar를 잃을 수 있다는 blocker를 확인했다. `MainV2ActivitySourceTest`와 `MainActivitySourceTest`에 sidecar 보존 테스트를 추가했고, 수정 전 `MainV2ActivitySourceTest.kt:26`, `MainActivitySourceTest.kt:13` assertion failure로 RED를 확인했다. 이후 deep link URL 파싱 결과를 `Bundle(bundle)` 위에 `putAll()`로 merge하고, query `room_id`/`chat_type` 보존 및 `MainActivity`의 `isUserCreatorChat()` 호환 판정을 추가했다. 재실행 결과 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.main.MainActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1` PASS를 확인했다. + - 2026-06-19: Task 12.5 코드 리뷰 및 검증으로 `SodaFirebaseMessagingService`, `DeepLinkActivity`, `MainActivity`, `MainV2Activity`의 deep link 수집/정규화/라우팅 분기와 source test를 재검토했다. 리뷰 중 `deep_link`만 있는 data payload에서 `sendNotification()` 호출 조건을 통과하지 못해 `ACTION_VIEW` data와 `deep_link` extra 보존 코드가 실행되지 않을 수 있는 blocker를 확인했다. `SodaFirebaseMessagingServiceSourceTest`에 `hasDeepLink(remoteMessage.data)` dispatch 조건 검증을 추가하고, `SodaFirebaseMessagingService`에 `deepLink`/`deep_link` 존재 시 기존 notification 생성 경로로 진입하는 최소 수정만 적용했다. 재검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.main.MainActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. 최초 Gradle 실행은 `~/.gradle` wrapper lock 파일 접근 제한으로 실패해 승인된 Gradle 실행으로 재검증했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning만 출력됐고, 승인된 `adb devices` 결과 연결 기기가 없어 실제 푸시 터치/deep link launch 수동 확인은 수행하지 못했다. + - 2026-06-19: 계약이 배포 전 `deep_link=${URISCHEME}://chat/{roomId}` 단독 payload로 확정되어 `chat_type` 호환 경로를 제거했다. `SodaFirebaseMessagingService`의 `chat_type` extra 복사, `DeepLinkActivity`/`MainActivity`/`MainV2Activity`의 `chat_type` query 수집과 `isUserCreatorChat()` 판정을 제거하고, DM 라우팅은 `deep_link_value == "chat"`만 사용하도록 정리했다. source test는 active source에 `chat_type`/`isUserCreatorChat`이 남지 않는지 검증하도록 변경했다. 재검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.main.MainActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. active main 코드 대상 `rg "chat_type|isUserCreatorChat|USER_CREATOR" ...` 결과는 없음이며, test 코드에는 부재 검증 assertion만 남는다. 최초 Gradle 실행은 `~/.gradle` wrapper lock 파일 접근 제한으로 실패해 승인된 Gradle 실행으로 재검증했고, `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning만 출력됐다. + ### Phase 13: WebSocket 전환 최종 검증과 수동 확인 - [x] **Task 13.1: DM 채팅 WebSocket 단위 테스트 실행** @@ -846,7 +879,7 @@ - Run: - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1` - Expected: - - USER_CREATOR push routing과 기존 deep link 회귀 테스트가 PASS. + - `deep_link=${URISCHEME}://chat/{roomId}` push routing과 기존 deep link 회귀 테스트가 PASS. - 검증 기록: - 2026-06-19: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1` 실행 결과 `BUILD SUCCESSFUL`을 확인했다. @@ -883,7 +916,7 @@ - 네트워크 오류 후 같은 채팅방 화면에 남아 있으면 reconnect 후 `JOIN_ROOM`과 누락 메시지 동기화가 수행된다. - 화면 밖에서는 reconnect가 예약/실행되지 않는다. - heartbeat `PING`/`PONG` timeout 시 연결 상태가 disconnected로 전환된다. - - USER_CREATOR push 터치 시 `room_id` 기준 DM 채팅방에 진입하고 일반 진입과 동일하게 `OpenRoom` 후 WebSocket join을 수행한다. + - DM push 터치 시 `deep_link=${URISCHEME}://chat/{roomId}` 기준 DM 채팅방에 진입하고 일반 진입과 동일하게 `OpenRoom` 후 WebSocket join을 수행한다. - 검증 기록: - 2026-06-19: `DmChatRoomViewModelTest`, `DmChatSocketClientTest`, `DmChatRemovedEndpointSourceTest`, `SodaFirebaseMessagingServiceSourceTest`, `DeepLinkActivitySourceTest`, `MainV2ActivitySourceTest`로 WebSocket join/send/ack/error/timeout/retry/reconnect/heartbeat, 제거 endpoint 미사용, USER_CREATOR push/deep link 라우팅의 자동 검증 PASS를 확인했다. 단, `adb devices` 결과 연결된 Android 기기가 없어 실제 앱 화면과 실제 서버/WebSocket을 통한 수동 확인은 수행하지 못했으므로 Task 13.4는 미완료로 유지한다. - 2026-06-19: Phase 13 코드 리뷰로 `DmChatRoomActivity`, `DmChatRoomViewModel`, `DmChatSocketClient`, `DmChatSocketModels`, `DmChatRepository`, `DmChatApi`의 WebSocket 연결/해제, `JOINED` 기준 연결 확정, `SEND_TEXT`/`SEND_ACK` pending 처리, timeout/retry/reconnect/heartbeat, 제거 REST endpoint 미사용 경로를 재검토했고 blocking issue는 발견하지 못했다. 재검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.fcm.*" --tests "kr.co.vividnext.sodalive.main.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. active DM main 코드의 제거 endpoint 문자열 검색 결과는 없음이며, DM test 경로까지 확장하면 삭제 검증 테스트와 과거 SSE 이력 테스트 fixture만 매칭된다. 최초 Gradle 실행은 `~/.gradle` wrapper lock 파일 접근 제한으로 실패해 승인된 Gradle 실행으로 재검증했고, `adb devices`는 승인된 실행에서도 연결된 기기가 없어 실제 앱/서버 WebSocket 수동 확인은 미완료로 유지한다. @@ -939,3 +972,4 @@ - 2026-06-18: `docs/20260610_DM_채팅화면/prd.md`의 WebSocket 전환 요구사항을 기준으로 `plan-task.md`를 보강했다. 기존 Phase 1~8의 SSE 구현 이력은 보존하고, Phase 9~13에 WebSocket 모델/클라이언트, Repository/DI 전환, `JOIN_ROOM`/`JOINED`, `MESSAGE`, `SEND_TEXT`/`SEND_ACK`, `LEAVE_ROOM`/close, reconnect/heartbeat/token refresh, USER_CREATOR push routing, 제거 endpoint 회귀 검증, 최종 수동 확인 작업을 추가했다. - 2026-06-18: 현재 코드 기준으로 `DmChatApi`의 `/messages/text`, `/events/disconnect`, `DmChatEventClient`, `DmChatRoomViewModel.sendText()` REST 전송, `disconnectRealtime()` SSE 해제 경로가 남아 있음을 확인했고, 이를 Phase 9~13에서 제거/교체할 대상으로 문서화했다. 이번 단계는 계획 문서 수정만 수행했으며 Android 구현, 빌드, 테스트는 실행하지 않았다. - 2026-06-18: Phase 10의 Task 10.1~10.3 코드 리뷰 및 검증을 수행했다. `DmChatRoomViewModel`의 `JOINED` 기준 연결 확정, WebSocket `MESSAGE` 병합, `requestId` 단위 pending/SEND_ACK/ERROR/timeout/retry 처리를 확인했고 blocking issue는 발견하지 못했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. `ktlintCheck`와 Gradle 실행에서는 기존 Gradle deprecation warning만 출력됐고 실패는 없었다. Task 10.4의 MESSAGE/SEND_ACK race와 Task 10.5 회귀 테스트 정리는 후속 미완료 범위로 유지한다. +- 2026-06-19: 사용자 제공 최신 FCM payload 계약을 반영해 파일 구조와 Phase 13 수동 확인 항목의 푸시 진입 기준을 `deep_link=${URISCHEME}://chat/{roomId}`로 갱신했다. Phase 12에는 기존 `chat_type`/`room_id` 기준 완료 이력을 보존한다는 주석을 추가하고, 새 계약 반영 범위를 미완료 `Task 12.5`로 추가했다. 이번 단계는 계획 문서 수정만 수행했으며 Android 구현, 빌드, 테스트는 실행하지 않았다. diff --git a/docs/20260610_DM_채팅화면/prd.md b/docs/20260610_DM_채팅화면/prd.md index 3e615823..b2626a2f 100644 --- a/docs/20260610_DM_채팅화면/prd.md +++ b/docs/20260610_DM_채팅화면/prd.md @@ -289,9 +289,11 @@ WebSocket 연결 상태를 유지하고, 네트워크 오류로 끊긴 경우 푸시 알림으로 DM 채팅방에 진입하는 흐름을 일반 진입과 동일한 WebSocket lifecycle로 연결한다. #### Requirements -- 푸시 알림을 터치하면 payload의 `chat_type == "USER_CREATOR"`와 `room_id`를 확인해 해당 채팅방 화면으로 이동한다. +- 푸시 알림을 터치하면 FCM payload의 `deep_link`만 사용해 해당 채팅방 화면으로 이동한다. +- DM 채팅방 푸시의 `deep_link` 형식은 `${URISCHEME}://chat/{roomId}`다. +- Android는 DM 푸시 진입 판단에 `chat_type` payload를 사용하지 않는다. - 푸시로 채팅방에 진입한 뒤에도 일반 진입과 동일하게 `OpenRoom` 호출 후 WebSocket 연결과 `JOIN_ROOM` 전송을 수행한다. -- `room_id`가 없거나 유효하지 않으면 DM 채팅방을 열지 않고 기존 푸시 오류 처리 정책을 따른다. +- `deep_link`가 없거나 `/chat/{roomId}`에서 유효한 `roomId`를 파싱할 수 없으면 DM 채팅방을 열지 않고 기존 푸시 오류 처리 정책을 따른다. ### Removed SSE Endpoints SSE 제거에 따라 네이티브 앱에서 더 이상 호출하면 안 되는 endpoint를 명시한다. @@ -390,7 +392,7 @@ data class UserCreatorChatMessageItemDto( - 네트워크 오류로 WebSocket이 끊기면 현재 채팅방 화면에 남아 있는 동안에만 재연결한다. - 재연결 성공 후 `JOIN_ROOM`을 다시 보내고 필요 시 GetMessages API로 누락 메시지를 동기화한다. - `PING`/`PONG` heartbeat timeout 시 연결 상태가 disconnected로 전환된다. -- 푸시 payload의 `chat_type == "USER_CREATOR"`와 `room_id` 기준으로 채팅방에 진입하고, 일반 진입과 동일하게 OpenRoom 및 WebSocket join을 수행한다. +- 푸시 payload의 `deep_link`가 `${URISCHEME}://chat/{roomId}` 형식이면 `roomId` 기준으로 채팅방에 진입하고, 일반 진입과 동일하게 OpenRoom 및 WebSocket join을 수행한다. - `GET /events`, `POST /events/disconnect`, `POST /messages/text`는 WebSocket 전환된 텍스트 송수신 경로에서 호출되지 않는다. - 제거 대상 UI(`character_type_badge`, `ll_can_badge`, `iv_more`, `notice_container`)가 DM 화면에 나타나지 않는다. - 크리에이터 채널 `DM 보내기`로 `creatorId` 기반 진입 시 `Cannot invoke setValue on a background thread` 예외 없이 DM 채팅방 Content 상태가 표시된다. @@ -433,3 +435,4 @@ data class UserCreatorChatMessageItemDto( - 2026-06-17: 사용자 제보 스택트레이스(`DmChatRoomViewModel.emitContent()`의 `MutableLiveData.setValue()` background thread 예외)와 `DmChatRoomViewModel.kt`의 `createRoomAndOpen()` Rx chain을 확인했다. 크리에이터 채널 `DM 보내기`의 `creatorId` 기반 진입에서 `CreateOrGetRoom` 후 `OpenRoom` 결과 처리 thread를 main thread로 보장해야 하는 요구사항을 기존 DM 채팅화면 PRD에 후속 범위로 누적했다. - 2026-06-18: 사용자 제공 WebSocket 전환 요구사항을 기준으로 기존 SSE 기반 PRD를 갱신했다. Core Features를 `WebSocket Room Session`, `WebSocket Message Receive`, `Send Text Message`, `Voice Message Send`, `Leave Room And Close`, `Reconnect And Heartbeat`, `Push Notification Entry`, `Removed SSE Endpoints`로 분리하고, 제거되는 SSE endpoint와 REST 텍스트 전송 endpoint가 더 이상 텍스트 실시간 송수신 경로에서 호출되지 않도록 성공 기준을 보강했다. - 2026-06-18: 이번 단계는 PRD 문서 수정만 수행했으며 Android 구현, plan-task 갱신, 빌드, 테스트는 실행하지 않았다. +- 2026-06-19: 사용자 제공 최신 FCM payload 계약을 반영해 Push Notification Entry 요구사항과 성공 기준을 `chat_type`/`room_id` 기준에서 `deep_link=${URISCHEME}://chat/{roomId}` 단독 기준으로 갱신했다. 이번 단계는 PRD 문서 수정만 수행했으며 Android 구현, 빌드, 테스트는 실행하지 않았다.