fix(live-room): 방장 종료 후 오래된 룸 정보 오류 노출을 막는다
This commit is contained in:
@@ -287,6 +287,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
private var blockedMemberIdList = Set<Int>()
|
||||
|
||||
private var hasInvokedJoinChannel = false
|
||||
private var hasDetectedHostOffline = false
|
||||
private var v2vMessageAssembler = V2vMessageAssembler()
|
||||
private var v2vAgentId: String?
|
||||
private var v2vSourceLanguage: String?
|
||||
@@ -322,6 +323,35 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
liveRoomInfo?.creatorId == UserDefaults.int(forKey: .userId)
|
||||
}
|
||||
|
||||
private func shouldSuppressMissingRoomInfoError(_ message: String?) -> Bool {
|
||||
let ignorableLiveRoomNotFoundMessages: Set<String> = [
|
||||
"해당하는 라이브의 정보가 없습니다.",
|
||||
"Live session information not found.",
|
||||
"該当するライブの情報がありません。"
|
||||
]
|
||||
|
||||
guard let message, ignorableLiveRoomNotFoundMessages.contains(message) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return liveRoomInfo != nil
|
||||
|| hasInvokedJoinChannel
|
||||
|| hasDetectedHostOffline
|
||||
|| AppState.shared.roomId == 0
|
||||
}
|
||||
|
||||
private func shouldRefreshRoomInfoOnMemberLeave(memberId: Int) -> Bool {
|
||||
guard let liveRoomInfo = liveRoomInfo else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard !hasDetectedHostOffline else {
|
||||
return false
|
||||
}
|
||||
|
||||
return liveRoomInfo.creatorId != memberId
|
||||
}
|
||||
|
||||
func stopV2VTranslationIfJoined(clearCaptionText: Bool = true) {
|
||||
guard isV2VJoined else { return }
|
||||
stopV2VTranslation(clearCaptionText: clearCaptionText)
|
||||
@@ -616,6 +646,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
let previousIsChatFrozen = self.isChatFrozen
|
||||
let syncedIsChatFrozen = data.isChatFrozen ?? false
|
||||
|
||||
self.hasDetectedHostOffline = false
|
||||
self.liveRoomInfo = data
|
||||
self.updateV2VAvailability(roomInfo: data)
|
||||
|
||||
@@ -661,6 +692,9 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
let nickname = getUserNicknameAndProfileUrl(accountId: userId).nickname
|
||||
onSuccess(nickname)
|
||||
}
|
||||
} else {
|
||||
if self.shouldSuppressMissingRoomInfoError(decoded.message) {
|
||||
DEBUG_LOG("Suppress stale getRoomInfo error during live-room teardown: \(decoded.message ?? "")")
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
@@ -670,6 +704,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
|
||||
self.isShowErrorPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
self.isLoading = false
|
||||
} catch {
|
||||
@@ -2973,12 +3008,13 @@ extension LiveRoomViewModel: AgoraRtcEngineDelegate {
|
||||
|
||||
if uid == UInt(creatorId) {
|
||||
// 라이브 종료
|
||||
self.hasDetectedHostOffline = true
|
||||
self.deInitAgoraEngine()
|
||||
self.liveRoomInfo = nil
|
||||
AppState.shared.errorMessage = I18n.LiveRoom.liveEndedMessage
|
||||
AppState.shared.isShowErrorPopup = true
|
||||
AppState.shared.roomId = 0
|
||||
} else {
|
||||
} else if self.shouldRefreshRoomInfoOnMemberLeave(memberId: Int(uid)) {
|
||||
// get room info
|
||||
self.getRoomInfo()
|
||||
}
|
||||
@@ -3265,7 +3301,7 @@ extension LiveRoomViewModel: AgoraRtmClientDelegate {
|
||||
}
|
||||
}
|
||||
} else if eventType == .remoteLeaveChannel {
|
||||
if let liveRoomInfo = liveRoomInfo, liveRoomInfo.creatorId != Int(memberId)! {
|
||||
if shouldRefreshRoomInfoOnMemberLeave(memberId: Int(memberId)!) {
|
||||
getRoomInfo()
|
||||
}
|
||||
}
|
||||
|
||||
41
docs/20260413_라이브룸방장부재시갱신호출차단.md
Normal file
41
docs/20260413_라이브룸방장부재시갱신호출차단.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 20260413 라이브룸 방장 부재 시 갱신 호출 차단
|
||||
|
||||
## 구현 체크리스트
|
||||
- [x] 라이브룸 오프라인/퇴장 이벤트와 `getRoomInfo()` 호출 경로 확인
|
||||
- [x] 방장 부재 상태에서 추가 `getRoomInfo()` 호출이 발생하지 않도록 최소 수정
|
||||
- [x] 종료 경쟁으로 내려오는 `라이브 정보가 없습니다.` 토스트 억제 범위 확인
|
||||
- [x] 변경 파일 정적 진단 및 빌드 검증
|
||||
- [x] 수동 검증 시나리오와 결과 기록
|
||||
|
||||
## 완료 기준 (Acceptance Criteria)
|
||||
- [ ] QA: 방장 퇴장 감지 시 참여자에게 `라이브가 종료되었습니다`가 표시되고 종료 흐름이 유지된다.
|
||||
- [ ] QA: 방장 퇴장 감지 이후 다른 참여자 퇴장 이벤트가 이어져도 `getRoomInfo()`가 추가 호출되지 않는다.
|
||||
- [ ] QA: 이미 입장한 라이브가 종료된 뒤 후속 `getRoomInfo()`가 실패해도 `라이브 정보가 없습니다.` 토스트는 표시되지 않는다.
|
||||
- [ ] QA: 일반 참여자 단독 퇴장 시 기존처럼 `getRoomInfo()` 갱신이 유지된다.
|
||||
|
||||
## 검증 기록
|
||||
- [2026-04-13] 무엇: `didOfflineOfUid` 및 `remoteLeaveChannel` 기반 `getRoomInfo()` 재호출 경로 조사
|
||||
- 왜: 방장 종료와 다른 참여자 종료가 겹칠 때 방장 부재 상태에서도 추가 갱신 호출이 발생할 수 있음
|
||||
- 어떻게: `SodaLive/Sources/Live/Room/LiveRoomViewModel.swift`의 RTC/RTM 오프라인 이벤트 분기와 `LiveRoomViewV2.swift` 초기 진입 흐름을 확인
|
||||
- [2026-04-13] 무엇: 방장 부재 감지 이후 추가 `getRoomInfo()` 호출 차단 구현
|
||||
- 왜: 방장 퇴장 직후 다른 참여자 퇴장 이벤트가 이어질 때 stale `creatorId` 기준으로 불필요한 갱신이 발생할 수 있음
|
||||
- 어떻게: `SodaLive/Sources/Live/Room/LiveRoomViewModel.swift`에 `hasDetectedHostOffline` 상태와 `shouldRefreshRoomInfoOnMemberLeave(memberId:)` 헬퍼를 추가하고, RTC `didOfflineOfUid` 및 RTM `remoteLeaveChannel`이 동일한 조건으로 `getRoomInfo()` 호출 여부를 판단하도록 수정
|
||||
- [2026-04-13] 실행 명령 및 결과
|
||||
- `lsp_diagnostics(SodaLive/Sources/Live/Room/LiveRoomViewModel.swift)` → `No diagnostics found`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build && xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` → 두 스킴 모두 `BUILD SUCCEEDED`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test; xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` → 두 스킴 모두 `Scheme ... is not currently configured for the test action.`
|
||||
- [2026-04-13] 수동 검증 시나리오
|
||||
- 시나리오: 방장 1명과 참여자 여러 명이 접속한 상태에서 방장 종료와 다른 참여자 퇴장이 연속 또는 동시 발생
|
||||
- 기대 결과: 방장 퇴장 감지 후에는 `라이브가 종료되었습니다` 종료 흐름만 유지되고, 후속 참여자 퇴장 이벤트로 `getRoomInfo()`가 재호출되지 않음
|
||||
- 현재 결과: CLI 환경에서는 실시간 다중 클라이언트/Agora 세션을 직접 구성할 수 없어 실제 앱 런타임 수동 검증은 별도 iOS 실행 환경에서 추가 확인 필요
|
||||
- [2026-04-13] 무엇: 종료 경쟁으로 반환된 `라이브 정보가 없습니다.` 토스트 억제 처리 추가
|
||||
- 왜: 일반 참여자 leave 신호가 먼저 처리되면 이미 종료된 라이브에 대한 `getRoomInfo()` 재조회가 실패하면서 서버 문구가 사용자 토스트로 그대로 노출될 수 있음
|
||||
- 어떻게: `SodaLive/Sources/Live/Room/LiveRoomViewModel.swift`에 `shouldSuppressMissingRoomInfoError(_:)` 헬퍼를 추가하고, `getRoomInfo()` 실패 분기에서 이미 입장했던 세션/종료 진행 상태의 `라이브 정보가 없습니다.`는 `DEBUG_LOG`만 남기고 토스트는 띄우지 않도록 최소 수정
|
||||
- [2026-04-13] 실행 명령 및 결과
|
||||
- `lsp_diagnostics(SodaLive/Sources/Live/Room/LiveRoomViewModel.swift)` → SourceKit 환경에서 `No such module 'Moya'` 반환
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build && xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` → 두 스킴 모두 `BUILD SUCCEEDED`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test; xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` → 두 스킴 모두 `Scheme ... is not currently configured for the test action.`
|
||||
- [2026-04-13] 수동 검증 시나리오
|
||||
- 시나리오: 이미 라이브에 입장한 참여자 상태에서 방 종료 직전 일반 참여자 leave 이벤트로 `getRoomInfo()`가 먼저 호출되고, 서버는 라이브 종료 상태를 반환
|
||||
- 기대 결과: `라이브 정보가 없습니다.` 토스트는 표시되지 않고, 기존 종료 흐름 또는 후속 종료 신호 처리만 유지됨
|
||||
- 현재 결과: CLI 환경에서는 실시간 Agora/다중 클라이언트 수동 재현이 불가능해 실제 앱 런타임 검증은 별도 iOS 환경에서 추가 확인 필요
|
||||
Reference in New Issue
Block a user