krz/rune
an ios client for njalla
clone: git clone https://gitbay.org/krz/rune.git
main: Rune/ViewModels/SettingsViewModel.swift · raw
1import Combine
2import Foundation
3
4@MainActor
5final class SettingsViewModel: ObservableObject {
6 @Published private(set) var client: NjallaClient?
7 @Published private(set) var balance: WalletBalance?
8 @Published private(set) var isAuthenticated = false
9 @Published private(set) var isBootstrapped = false
10 @Published private(set) var isLoadingBalance = false
11 @Published var errorMessage: String?
12
13 private let keychainManager = KeychainManager()
14
15 var requiresOnboarding: Bool {
16 isBootstrapped && !isAuthenticated
17 }
18
19 func bootstrap() async {
20 guard !isBootstrapped else { return }
21
22 defer {
23 isBootstrapped = true
24 }
25
26 do {
27 guard let token = try keychainManager.readToken(), !token.isEmpty else {
28 isAuthenticated = false
29 client = nil
30 balance = nil
31 return
32 }
33
34 let client = NjallaClient(token: token)
35 self.client = client
36 isAuthenticated = true
37 try await refreshBalance()
38 } catch is CancellationError {
39 return
40 } catch {
41 if (error as? URLError)?.code == .cancelled {
42 return
43 }
44 errorMessage = error.localizedDescription
45 client = nil
46 balance = nil
47 isAuthenticated = false
48 }
49 }
50
51 func login(token: String) async throws {
52 let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
53 let client = NjallaClient(token: trimmedToken)
54 let balance = try await client.getBalance()
55 try keychainManager.saveToken(trimmedToken)
56
57 self.client = client
58 self.balance = balance
59 isAuthenticated = true
60 isBootstrapped = true
61 errorMessage = nil
62 }
63
64 func refreshBalance() async throws {
65 guard let client else { return }
66
67 isLoadingBalance = true
68 defer {
69 isLoadingBalance = false
70 }
71
72 do {
73 balance = try await client.getBalance()
74 errorMessage = nil
75 } catch is CancellationError {
76 throw CancellationError()
77 } catch {
78 if (error as? URLError)?.code == .cancelled {
79 throw error
80 }
81 errorMessage = error.localizedDescription
82 throw error
83 }
84 }
85
86 func logout() async throws {
87 if let client {
88 try await client.logout()
89 }
90 try keychainManager.deleteToken()
91 clearLocalSession()
92 }
93
94 func logoutIfCurrentTokenDeleted(_ key: String) throws -> Bool {
95 guard let storedToken = try keychainManager.readToken(), storedToken == key else {
96 return false
97 }
98
99 try keychainManager.deleteToken()
100 clearLocalSession()
101 return true
102 }
103
104 private func clearLocalSession() {
105 client = nil
106 balance = nil
107 isAuthenticated = false
108 errorMessage = nil
109 }
110}