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}