Ivan Valkou 5 лет назад
Родитель
Сommit
13590d2e20

+ 1 - 1
FreeAPS/Sources/Containers/NetworkContainer.swift

@@ -5,7 +5,7 @@ private let resolver = FreeAPSApp.resolver
 
 enum NetworkContainer: DependeciesContainer {
     static func register(container: Container) {
-        container.register(NetworkManager.self) { _ in BaseNetworkManager() }
+        container.register(NetworkManager.self) { _ in BaseNetworkManager(resolver: resolver) }
         container.register(AuthorizationManager.self) { _ in BaseAuthorizationManager(resolver: resolver) }
     }
 }

+ 6 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigDataFlow.swift

@@ -1,3 +1,6 @@
+import Combine
+import Foundation
+
 enum NightscoutConfig {
     enum Config {
         static let urlKey = "NightscoutConfig.url"
@@ -5,4 +8,6 @@ enum NightscoutConfig {
     }
 }
 
-protocol NightscoutConfigProvider: Provider {}
+protocol NightscoutConfigProvider: Provider {
+    func checkConnection(url: URL, secret: String) -> AnyPublisher<Void, Error>
+}

+ 8 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigProvider.swift

@@ -1,3 +1,10 @@
+import Combine
+import Foundation
+
 extension NightscoutConfig {
-    final class Provider: BaseProvider, NightscoutConfigProvider {}
+    final class Provider: BaseProvider, NightscoutConfigProvider {
+        func checkConnection(url: URL, secret: String) -> AnyPublisher<Void, Error> {
+            NightscoutAPI(url: url, secret: secret).checkConnection()
+        }
+    }
 }

+ 24 - 3
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigViewModel.swift

@@ -1,3 +1,4 @@
+import Combine
 import SwiftUI
 
 extension NightscoutConfig {
@@ -6,6 +7,8 @@ extension NightscoutConfig {
 
         @Published var url = ""
         @Published var secret = ""
+        @Published var message = ""
+        @Published var connecting = false
 
         override func subscribe() {
             url = keychain.getValue(String.self, forKey: Config.urlKey) ?? ""
@@ -13,9 +16,27 @@ extension NightscoutConfig {
         }
 
         func connect() {
-            // TODO: check connection
-            keychain.setValue(url, forKey: Config.urlKey)
-            keychain.setValue(url, forKey: Config.secretKey)
+            guard let url = URL(string: url) else {
+                message = "Invalid URL"
+                return
+            }
+            connecting = true
+            message = ""
+            provider.checkConnection(url: url, secret: secret)
+                .receive(on: DispatchQueue.main)
+                .sink { completion in
+                    switch completion {
+                    case .finished: break
+                    case let .failure(error):
+                        self.message = "Error: \(error.localizedDescription)"
+                    }
+                    self.connecting = false
+                } receiveValue: {
+                    self.message = "Connected!"
+                    self.keychain.setValue(self.url, forKey: Config.urlKey)
+                    self.keychain.setValue(self.secret, forKey: Config.secretKey)
+                }
+                .store(in: &lifetime)
         }
 
         func delete() {

+ 28 - 12
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -6,18 +6,34 @@ extension NightscoutConfig {
 
         var body: some View {
             Form {
-                TextField("URL", text: $viewModel.url)
-                    .disableAutocorrection(true)
-                    .textContentType(.URL)
-                    .autocapitalization(.none)
-                    .keyboardType(.URL)
-                SecureField("API secret", text: $viewModel.secret)
-                    .disableAutocorrection(true)
-                    .autocapitalization(.none)
-                    .textContentType(.password)
-                    .keyboardType(.asciiCapable)
-                Button("Connect") { viewModel.connect() }.disabled(viewModel.url.isEmpty || viewModel.secret.isEmpty)
-                Button("Delete") { viewModel.delete() }.foregroundColor(.red)
+                Section {
+                    TextField("URL", text: $viewModel.url)
+                        .disableAutocorrection(true)
+                        .textContentType(.URL)
+                        .autocapitalization(.none)
+                        .keyboardType(.URL)
+                    SecureField("API secret", text: $viewModel.secret)
+                        .disableAutocorrection(true)
+                        .autocapitalization(.none)
+                        .textContentType(.password)
+                        .keyboardType(.asciiCapable)
+                    if !viewModel.message.isEmpty {
+                        Text(viewModel.message)
+                    }
+                    if viewModel.connecting {
+                        HStack {
+                            Text("Connecting...")
+                            Spacer()
+                            ProgressView()
+                        }
+                    }
+                }
+
+                Section {
+                    Button("Connect") { viewModel.connect() }
+                        .disabled(viewModel.url.isEmpty || viewModel.secret.isEmpty || viewModel.connecting)
+                    Button("Delete") { viewModel.delete() }.foregroundColor(.red).disabled(viewModel.connecting)
+                }
             }
             .toolbar { ToolbarItem(placement: .principal) { Text("Nightscout Config") } }
             .navigationBarItems(leading: Button("Close", action: viewModel.hideModal))

+ 18 - 1
FreeAPS/Sources/Services/Network/NetworkManager.swift

@@ -1,6 +1,23 @@
 import Combine
 import Foundation
+import Swinject
 
 protocol NetworkManager {}
 
-final class BaseNetworkManager: NetworkManager {}
+final class BaseNetworkManager: NetworkManager, Injectable {
+    @Injected() private var keychain: Keychain!
+
+    private var nightscoutAPI: NightscoutAPI? {
+        guard let urlString = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey),
+              let url = URL(string: urlString),
+              let secret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey)
+        else {
+            return nil
+        }
+        return NightscoutAPI(url: url, secret: secret)
+    }
+
+    init(resolver: Resolver) {
+        injectServices(resolver)
+    }
+}

+ 17 - 8
FreeAPS/Sources/Services/Network/NetworkService.swift

@@ -1,18 +1,27 @@
 import Combine
 import Foundation
 
-struct NetworkService {
-    struct Response<T> {
-        let value: T
-        let response: URLResponse
+enum NetworkError: Error, LocalizedError {
+    case badStatusCode(HTTPResponseStatus)
+
+    var errorDescription: String? {
+        switch self {
+        case let .badStatusCode(code):
+            return code.reasonPhrase
+        }
     }
+}
 
-    func run<T: Decodable>(_ request: URLRequest, decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<T>, Error> {
+struct NetworkService {
+    func run(_ request: URLRequest) -> AnyPublisher<Data, Error> {
         URLSession.shared
             .dataTaskPublisher(for: request)
-            .tryMap { result -> Response<T> in
-                let value = try decoder.decode(T.self, from: result.data)
-                return Response(value: value, response: result.response)
+            .tryMap { data, response in
+                let code = (response as! HTTPURLResponse).statusCode
+                guard 200 ..< 300 ~= code else {
+                    throw NetworkError.badStatusCode(.init(statusCode: code))
+                }
+                return data
             }
             .eraseToAnyPublisher()
     }

+ 35 - 2
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -1,7 +1,40 @@
+import Combine
+import CommonCrypto
 import Foundation
 
 struct NightscoutAPI {
-    let base: URL
+    let url: URL
+    let secret: String
+    private let service = NetworkService()
 }
 
-extension NightscoutAPI {}
+extension NightscoutAPI {
+    func checkConnection() -> AnyPublisher<Void, Error> {
+        struct Check: Codable, Equatable {
+            var eventType = "Note"
+            var enteredBy = "feeaps-x://"
+            var notes = "FreeAPS connected"
+        }
+        let check = Check()
+        var request = URLRequest(url: url.appendingPathComponent("api/v1/treatments.json"))
+        request.httpMethod = "POST"
+        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+        request.addValue(secret.sha1(), forHTTPHeaderField: "api-secret")
+        request.httpBody = try! JSONEncoder().encode(check)
+        return service.run(request)
+            .map { _ in () }
+            .eraseToAnyPublisher()
+    }
+}
+
+private extension String {
+    func sha1() -> String {
+        let data = Data(utf8)
+        var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
+        data.withUnsafeBytes {
+            _ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
+        }
+        let hexBytes = digest.map { String(format: "%02hhx", $0) }
+        return hexBytes.joined()
+    }
+}