gitbay/Account/WebSessions.swift
66 lines · 1886 bytes
6 symbols in this file
1import Foundation
2import Observation
3
4/// One row of `web sessions list`: a browser signed in to the web.
5nonisolated struct WebSession: Decodable, Sendable, Hashable, Identifiable {
6 /// The first characters of the session's hash, which `revoke` takes.
7 let id: String
8 let createdAt: Date
9 let expiresAt: Date
10 let lastUsedAt: Date
11
12 enum CodingKeys: String, CodingKey {
13 case id
14 case createdAt = "created_at"
15 case expiresAt = "expires_at"
16 case lastUsedAt = "last_used_at"
17 }
18}
19
20/// `web sessions list` and `revoke`. The app signs in with an API token,
21/// so ending a browser session never signs the app out.
22@Observable
23@MainActor
24final class WebSessionsViewModel {
25
26 private(set) var state: LoadState<[WebSession]> = .loading
27 private(set) var actionError: String?
28 private(set) var working = false
29
30 private let client: GitbayClient
31
32 init(client: GitbayClient) {
33 self.client = client
34 }
35
36 func load() async {
37 do {
38 let sessions = try await client.readList(["web", "sessions", "list"], of: WebSession.self)
39 state = sessions.isEmpty ? .empty("No browser is signed in.") : .loaded(sessions)
40 } catch {
41 state = .from(error)
42 }
43 }
44
45 func revoke(id: String) async {
46 await perform(["web", "sessions", "revoke", id])
47 }
48
49 func revokeAll() async {
50 await perform(["web", "sessions", "revoke", "--all"])
51 }
52
53 private func perform(_ argv: [String]) async {
54 working = true
55 actionError = nil
56 defer { working = false }
57 do {
58 try await client.run(argv)
59 await load()
60 } catch let error as GitbayError {
61 actionError = error.userFacingMessage
62 } catch {
63 actionError = GitbayError.transport(error).userFacingMessage
64 }
65 }
66}