Files
hermes/mobile/ios-app/App/HermesRepository.swift
T

146 lines
4.4 KiB
Swift

import Combine
import Foundation
@MainActor
final class HermesRepository: ObservableObject {
@Published private(set) var currentSession: HermesSessionResponse?
@Published private(set) var currentRound: HermesRound?
@Published private(set) var isLoading = true
@Published private(set) var errorCause: Error?
@Published private(set) var serverClockOffset: TimeInterval?
private let apiClient: HermesAPIClient
init(apiClient: HermesAPIClient) {
self.apiClient = apiClient
}
func bootstrap(_ request: HermesSessionStartRequest) async throws -> HermesSessionResponse {
isLoading = true
errorCause = nil
do {
await syncClock()
let session: HermesSessionResponse
if let existingSession = currentSession {
session = existingSession
} else {
session = try await startSession(request)
}
if currentRound == nil {
currentRound = try await loadRoundFromNetwork()
}
isLoading = false
return session
} catch {
errorCause = error
isLoading = false
throw error
}
}
func refreshRoundFromNetwork() async throws -> HermesRound {
isLoading = true
errorCause = nil
do {
await syncClock()
let round = try await loadRoundFromNetwork()
currentRound = round
isLoading = false
return round
} catch {
errorCause = error
isLoading = false
throw error
}
}
func startSession(_ request: HermesSessionStartRequest) async throws -> HermesSessionResponse {
let session = try await apiClient.startSession(request)
currentSession = session
return session
}
func endSession() async throws -> HermesSessionResponse {
let session = try await apiClient.endSession()
currentSession = session
return session
}
func submitBetIntent(_ request: HermesBetIntentRequest) async throws -> HermesBetIntentResponse {
try await apiClient.submitBetIntent(request)
}
func currentOdds(marketID: UUID) async throws -> HermesOddsVersion {
try await apiClient.currentOdds(marketID: marketID)
}
func settlement(eventID: UUID) async throws -> HermesSettlement {
try await apiClient.settlement(eventID: eventID)
}
func experimentConfig() async throws -> HermesExperimentConfig {
try await apiClient.experimentConfig()
}
func localization(localeCode: String) async throws -> HermesLocalizationBundle {
try await apiClient.localization(localeCode: localeCode)
}
func submitAnalyticsBatch(_ payload: HermesAnalyticsBatchRequest) async throws {
try await apiClient.submitAnalyticsBatch(payload)
}
func serverNow() -> Date {
guard let serverClockOffset else {
return Date()
}
return Date().addingTimeInterval(serverClockOffset)
}
private func syncClock() async {
do {
let health = try await apiClient.health()
serverClockOffset = health.serverTime.timeIntervalSince(Date())
} catch {
return
}
}
private func loadRoundFromNetwork() async throws -> HermesRound {
let event = try await apiClient.nextEvent()
let manifest = try await apiClient.eventManifest(eventID: event.id)
guard let media = manifest.media.first(where: { $0.mediaType == "hls_main" }) ?? manifest.media.first else {
throw HermesAPIError.invalidResponse
}
let market: HermesMarket
if let manifestMarket = manifest.markets.first {
market = manifestMarket
} else {
let markets = try await apiClient.markets(eventID: event.id)
guard let fallbackMarket = markets.first else {
throw HermesAPIError.invalidResponse
}
market = fallbackMarket
}
let oddsVersion = try await apiClient.currentOdds(marketID: market.id)
let settlement = try await apiClient.settlement(eventID: event.id)
return HermesRound(
event: event,
media: media,
market: market,
oddsVersion: oddsVersion,
settlement: settlement
)
}
}