NightscoutSettingsViewModel.swift 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // LoopFollow
  2. // NightscoutSettingsViewModel.swift
  3. import Combine
  4. import Foundation
  5. protocol NightscoutSettingsViewModelDelegate: AnyObject {
  6. func nightscoutSettingsDidFinish()
  7. }
  8. class NightscoutSettingsViewModel: ObservableObject {
  9. weak var delegate: NightscoutSettingsViewModelDelegate?
  10. private var initialURL: String
  11. private var initialToken: String
  12. @Published var nightscoutURL: String = Storage.shared.url.value {
  13. willSet {
  14. if newValue != nightscoutURL {
  15. Storage.shared.url.value = newValue
  16. triggerCheckStatus()
  17. }
  18. }
  19. }
  20. @Published var nightscoutToken: String = Storage.shared.token.value {
  21. willSet {
  22. if newValue != nightscoutToken {
  23. Storage.shared.token.value = newValue
  24. triggerCheckStatus()
  25. }
  26. }
  27. }
  28. @Published var nightscoutStatus: String = "Checking..."
  29. private var cancellables = Set<AnyCancellable>()
  30. private var checkStatusSubject = PassthroughSubject<Void, Never>()
  31. private var checkStatusWorkItem: DispatchWorkItem?
  32. init() {
  33. initialURL = Storage.shared.url.value
  34. initialToken = Storage.shared.token.value
  35. setupDebounce()
  36. checkNightscoutStatus()
  37. }
  38. private func setupDebounce() {
  39. checkStatusSubject
  40. .debounce(for: .seconds(2), scheduler: DispatchQueue.main)
  41. .sink { [weak self] in
  42. self?.checkNightscoutStatus()
  43. }
  44. .store(in: &cancellables)
  45. }
  46. private func triggerCheckStatus() {
  47. checkStatusWorkItem?.cancel()
  48. nightscoutStatus = "Checking..."
  49. checkStatusWorkItem = DispatchWorkItem {
  50. self.checkStatusSubject.send()
  51. }
  52. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: checkStatusWorkItem!)
  53. }
  54. func processURL(_ value: String) {
  55. var useTokenUrl = false
  56. if let urlComponents = URLComponents(string: value), let queryItems = urlComponents.queryItems {
  57. if let tokenItem = queryItems.first(where: { $0.name.lowercased() == "token" }) {
  58. let tokenPattern = "^[^-\\s]+-[0-9a-fA-F]{16}$"
  59. if let token = tokenItem.value, let _ = token.range(of: tokenPattern, options: .regularExpression) {
  60. var baseComponents = urlComponents
  61. baseComponents.queryItems = nil
  62. if let baseURL = baseComponents.string {
  63. nightscoutToken = token
  64. nightscoutURL = baseURL
  65. useTokenUrl = true
  66. }
  67. }
  68. }
  69. }
  70. if !useTokenUrl {
  71. let filtered = value.replacingOccurrences(of: "[^A-Za-z0-9:/._-]", with: "", options: .regularExpression).lowercased()
  72. var cleanURL = filtered
  73. while cleanURL.count > 8, cleanURL.last == "/" {
  74. cleanURL = String(cleanURL.dropLast())
  75. }
  76. nightscoutURL = cleanURL
  77. }
  78. }
  79. func checkNightscoutStatus() {
  80. NightscoutUtils.verifyURLAndToken { error, _, nsWriteAuth, nsAdminAuth in
  81. DispatchQueue.main.async {
  82. Storage.shared.nsWriteAuth.value = nsWriteAuth
  83. Storage.shared.nsAdminAuth.value = nsAdminAuth
  84. self.updateStatusLabel(error: error)
  85. }
  86. }
  87. }
  88. func updateStatusLabel(error: NightscoutUtils.NightscoutError?) {
  89. if let error = error {
  90. switch error {
  91. case .invalidURL:
  92. nightscoutStatus = "Invalid URL"
  93. case .networkError:
  94. nightscoutStatus = "Network Error"
  95. case .invalidToken:
  96. nightscoutStatus = "Invalid Token"
  97. case .tokenRequired:
  98. nightscoutStatus = "Token Required"
  99. case .siteNotFound:
  100. nightscoutStatus = "Site Not Found"
  101. case .unknown:
  102. nightscoutStatus = "Unknown Error"
  103. case .emptyAddress:
  104. nightscoutStatus = "Address Empty"
  105. }
  106. } else {
  107. let authStatus: String
  108. if Storage.shared.nsAdminAuth.value {
  109. authStatus = "Admin"
  110. } else {
  111. authStatus = "Read" + (Storage.shared.nsWriteAuth.value ? " & Write" : "")
  112. }
  113. nightscoutStatus = "OK (\(authStatus))"
  114. if nightscoutURL != initialURL || nightscoutToken != initialToken {
  115. NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil)
  116. }
  117. }
  118. }
  119. func dismiss() {
  120. delegate?.nightscoutSettingsDidFinish()
  121. }
  122. }