docs(dm): DM push deep_link 계약을 기록한다

This commit is contained in:
2026-06-19 05:03:37 +09:00
parent da64806d88
commit dacd3a67a1
2 changed files with 45 additions and 8 deletions

View File

@@ -73,11 +73,11 @@
- Modify: `app/src/main/AndroidManifest.xml` - Modify: `app/src/main/AndroidManifest.xml`
- `DmChatRoomActivity`를 등록한다. - `DmChatRoomActivity`를 등록한다.
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt` - 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` - 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` - 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` - Modify: `docs/agent-guides/build-test-style.md`
- 신규 DM 채팅 테스트 단일 실행 예시를 추가한다. - 신규 DM 채팅 테스트 단일 실행 예시를 추가한다.
- Test Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatMapperTest.kt` - Test Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatMapperTest.kt`
@@ -755,6 +755,8 @@
### Phase 12: 푸시 진입과 제거 endpoint 회귀 검증 ### 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 전달 추가** - [x] **Task 12.1: FCM payload에 chat_type 전달 추가**
- Files: - Files:
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/fcm/SodaFirebaseMessagingService.kt` - 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-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은 출력됐지만 실패는 없었다. - 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 전환 최종 검증과 수동 확인 ### Phase 13: WebSocket 전환 최종 검증과 수동 확인
- [x] **Task 13.1: DM 채팅 WebSocket 단위 테스트 실행** - [x] **Task 13.1: DM 채팅 WebSocket 단위 테스트 실행**
@@ -846,7 +879,7 @@
- Run: - 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` - `./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: - 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`을 확인했다. - 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 후 `JOIN_ROOM`과 누락 메시지 동기화가 수행된다.
- 화면 밖에서는 reconnect가 예약/실행되지 않는다. - 화면 밖에서는 reconnect가 예약/실행되지 않는다.
- heartbeat `PING`/`PONG` timeout 시 연결 상태가 disconnected로 전환된다. - 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: `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 수동 확인은 미완료로 유지한다. - 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: `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: 현재 코드 기준으로 `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-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 구현, 빌드, 테스트는 실행하지 않았다.

View File

@@ -289,9 +289,11 @@ WebSocket 연결 상태를 유지하고, 네트워크 오류로 끊긴 경우
푸시 알림으로 DM 채팅방에 진입하는 흐름을 일반 진입과 동일한 WebSocket lifecycle로 연결한다. 푸시 알림으로 DM 채팅방에 진입하는 흐름을 일반 진입과 동일한 WebSocket lifecycle로 연결한다.
#### Requirements #### 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` 전송을 수행한다. - 푸시로 채팅방에 진입한 뒤에도 일반 진입과 동일하게 `OpenRoom` 호출 후 WebSocket 연결과 `JOIN_ROOM` 전송을 수행한다.
- `room_id`가 없거나 유효하지 않으면 DM 채팅방을 열지 않고 기존 푸시 오류 처리 정책을 따른다. - `deep_link`가 없거나 `/chat/{roomId}`에서 유효한 `roomId`를 파싱할 수 없으면 DM 채팅방을 열지 않고 기존 푸시 오류 처리 정책을 따른다.
### Removed SSE Endpoints ### Removed SSE Endpoints
SSE 제거에 따라 네이티브 앱에서 더 이상 호출하면 안 되는 endpoint를 명시한다. SSE 제거에 따라 네이티브 앱에서 더 이상 호출하면 안 되는 endpoint를 명시한다.
@@ -390,7 +392,7 @@ data class UserCreatorChatMessageItemDto(
- 네트워크 오류로 WebSocket이 끊기면 현재 채팅방 화면에 남아 있는 동안에만 재연결한다. - 네트워크 오류로 WebSocket이 끊기면 현재 채팅방 화면에 남아 있는 동안에만 재연결한다.
- 재연결 성공 후 `JOIN_ROOM`을 다시 보내고 필요 시 GetMessages API로 누락 메시지를 동기화한다. - 재연결 성공 후 `JOIN_ROOM`을 다시 보내고 필요 시 GetMessages API로 누락 메시지를 동기화한다.
- `PING`/`PONG` heartbeat timeout 시 연결 상태가 disconnected로 전환된다. - `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 전환된 텍스트 송수신 경로에서 호출되지 않는다. - `GET /events`, `POST /events/disconnect`, `POST /messages/text`는 WebSocket 전환된 텍스트 송수신 경로에서 호출되지 않는다.
- 제거 대상 UI(`character_type_badge`, `ll_can_badge`, `iv_more`, `notice_container`)가 DM 화면에 나타나지 않는다. - 제거 대상 UI(`character_type_badge`, `ll_can_badge`, `iv_more`, `notice_container`)가 DM 화면에 나타나지 않는다.
- 크리에이터 채널 `DM 보내기``creatorId` 기반 진입 시 `Cannot invoke setValue on a background thread` 예외 없이 DM 채팅방 Content 상태가 표시된다. - 크리에이터 채널 `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-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: 사용자 제공 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-18: 이번 단계는 PRD 문서 수정만 수행했으며 Android 구현, plan-task 갱신, 빌드, 테스트는 실행하지 않았다.
- 2026-06-19: 사용자 제공 최신 FCM payload 계약을 반영해 Push Notification Entry 요구사항과 성공 기준을 `chat_type`/`room_id` 기준에서 `deep_link=${URISCHEME}://chat/{roomId}` 단독 기준으로 갱신했다. 이번 단계는 PRD 문서 수정만 수행했으며 Android 구현, 빌드, 테스트는 실행하지 않았다.