// // Copyright 2026 Element Creations Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. // Please see LICENSE files in the repository root for full details. // import Foundation // sourcery: AutoMockable protocol HealthChatOpenIDTokenProviderProtocol { /// A valid OpenID access token, from cache when possible. func validToken() async -> Result /// Drops the cached token, e.g. after the API rejected it. func invalidateToken() } final class HealthChatOpenIDTokenProvider: HealthChatOpenIDTokenProviderProtocol { private let clientProxy: ClientProxyProtocol private let dateProvider: () -> Date /// Tokens this close to expiry are refreshed early, matching the web client's buffer. private static let expiryBuffer: TimeInterval = 60 private var cachedToken: (token: String, expiry: Date)? init(clientProxy: ClientProxyProtocol, dateProvider: @escaping () -> Date = Date.init) { self.clientProxy = clientProxy self.dateProvider = dateProvider } func validToken() async -> Result { if let cachedToken, dateProvider().addingTimeInterval(Self.expiryBuffer) < cachedToken.expiry { return .success(cachedToken.token) } switch await clientProxy.requestOpenIDToken() { case .success(let token): cachedToken = (token.accessToken, dateProvider().addingTimeInterval(token.expiresIn)) return .success(token.accessToken) case .failure: return .failure(.tokenAcquisitionFailed) } } func invalidateToken() { cachedToken = nil } }