라이브룸에서 진행자 언어와 기기 언어가 다를 때 자막 토글을 제공한다. 룸 정보 응답에 V2V 워커 토큰과 진행자 언어 코드를 포함한다. Agora V2V 에이전트 참여와 종료 API 연동을 추가한다
129 lines
4.1 KiB
Swift
129 lines
4.1 KiB
Swift
//
|
|
// V2vRepository.swift
|
|
// SodaLive
|
|
//
|
|
// Created by klaus on 2/9/26.
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
import Moya
|
|
import CombineMoya
|
|
|
|
protocol V2VRepository {
|
|
func join(request: V2VJoinRequest) -> AnyPublisher<String, V2VRepositoryError>
|
|
func leave(agentId: String) -> AnyPublisher<Void, V2VRepositoryError>
|
|
}
|
|
|
|
enum V2VRepositoryError: Error {
|
|
case network(message: String)
|
|
case decoding
|
|
case business(message: String)
|
|
|
|
var userMessage: String {
|
|
switch self {
|
|
case .network(let message):
|
|
return message
|
|
case .decoding:
|
|
return I18n.Common.commonError
|
|
case .business(let message):
|
|
return message
|
|
}
|
|
}
|
|
}
|
|
|
|
final class V2VRepositoryImpl: V2VRepository {
|
|
private let api: MoyaProvider<V2vApi>
|
|
|
|
init(api: MoyaProvider<V2vApi> = MoyaProvider<V2vApi>()) {
|
|
self.api = api
|
|
}
|
|
|
|
func join(request: V2VJoinRequest) -> AnyPublisher<String, V2VRepositoryError> {
|
|
api.requestPublisher(.join(request: request))
|
|
.tryMap { response in
|
|
try Self.validateStatusCode(response)
|
|
|
|
do {
|
|
let decoded = try JSONDecoder().decode(V2VJoinResponse.self, from: response.data)
|
|
guard !decoded.agentId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
throw V2VRepositoryError.decoding
|
|
}
|
|
return decoded.agentId
|
|
} catch let error as V2VRepositoryError {
|
|
throw error
|
|
} catch {
|
|
throw V2VRepositoryError.decoding
|
|
}
|
|
}
|
|
.mapError { Self.mapError($0) }
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
func leave(agentId: String) -> AnyPublisher<Void, V2VRepositoryError> {
|
|
api.requestPublisher(.leave(agentId: agentId))
|
|
.tryMap { response in
|
|
try Self.validateStatusCode(response)
|
|
if !response.data.isEmpty {
|
|
if let text = String(data: response.data, encoding: .utf8) {
|
|
DEBUG_LOG("[V2V] leave response: \(text)")
|
|
}
|
|
_ = try? JSONDecoder().decode(V2VLeaveResponse.self, from: response.data)
|
|
}
|
|
return ()
|
|
}
|
|
.mapError { Self.mapError($0) }
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
private static func validateStatusCode(_ response: Response) throws {
|
|
guard (200..<300).contains(response.statusCode) else {
|
|
throw V2VRepositoryError.business(message: parseBusinessMessage(from: response.data) ?? I18n.Common.commonError)
|
|
}
|
|
}
|
|
|
|
private static func mapError(_ error: Error) -> V2VRepositoryError {
|
|
if let mapped = error as? V2VRepositoryError {
|
|
return mapped
|
|
}
|
|
|
|
guard let moyaError = error as? MoyaError else {
|
|
return .network(message: I18n.Common.commonError)
|
|
}
|
|
|
|
switch moyaError {
|
|
case .statusCode(let response):
|
|
if let message = parseBusinessMessage(from: response.data) {
|
|
return .business(message: message)
|
|
}
|
|
return .business(message: I18n.Common.commonError)
|
|
|
|
case .objectMapping, .jsonMapping, .encodableMapping, .stringMapping, .imageMapping:
|
|
return .decoding
|
|
|
|
default:
|
|
return .network(message: I18n.Common.commonError)
|
|
}
|
|
}
|
|
|
|
private static func parseBusinessMessage(from data: Data) -> String? {
|
|
if let decoded = try? JSONDecoder().decode(V2VErrorResponse.self, from: data),
|
|
let message = decoded.message?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!message.isEmpty {
|
|
return message
|
|
}
|
|
|
|
if let decoded = try? JSONDecoder().decode(ApiResponseWithoutData.self, from: data),
|
|
let message = decoded.message?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!message.isEmpty {
|
|
return message
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private struct V2VErrorResponse: Decodable {
|
|
let message: String?
|
|
}
|