import Foundation import Observation /// One row of `web sessions list`: a browser signed in to the web. nonisolated struct WebSession: Decodable, Sendable, Hashable, Identifiable { /// The first characters of the session's hash, which `revoke` takes. let id: String let createdAt: Date let expiresAt: Date let lastUsedAt: Date enum CodingKeys: String, CodingKey { case id case createdAt = "created_at" case expiresAt = "expires_at" case lastUsedAt = "last_used_at" } } /// `web sessions list` and `revoke`. The app signs in with an API token, /// so ending a browser session never signs the app out. @Observable @MainActor final class WebSessionsViewModel { private(set) var state: LoadState<[WebSession]> = .loading private(set) var actionError: String? private(set) var working = false private let client: GitbayClient init(client: GitbayClient) { self.client = client } func load() async { do { let sessions = try await client.readList(["web", "sessions", "list"], of: WebSession.self) state = sessions.isEmpty ? .empty("No browser is signed in.") : .loaded(sessions) } catch { state = .from(error) } } func revoke(id: String) async { await perform(["web", "sessions", "revoke", id]) } func revokeAll() async { await perform(["web", "sessions", "revoke", "--all"]) } private func perform(_ argv: [String]) async { working = true actionError = nil defer { working = false } do { try await client.run(argv) await load() } catch let error as GitbayError { actionError = error.userFacingMessage } catch { actionError = GitbayError.transport(error).userFacingMessage } } }