From b6a1778460c752c354b2e279f0b64861abf6d651 Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Sun, 12 Jan 2025 10:37:30 -0800 Subject: [PATCH 01/10] Fixes networking for the Apple Watch Signed-off-by: Dan Cunningham --- NotificationService/NotificationService.swift | 4 +- .../Sources/OpenHABCore/Util/HTTPClient.swift | 141 +++++++++++++----- .../OpenHABCore/Util/NetworkConnection.swift | 5 +- .../OpenHABCore/Util/NetworkTracker.swift | 20 ++- .../OpenHABCore/Util/OpenHABItemCache.swift | 2 +- .../OpenHABCore/Util/Preferences.swift | 33 +++- .../openHABWatch (Notification).xcscheme | 2 +- .../xcschemes/openHABWatch.xcscheme | 15 +- .../openHABWatchSwift (Complication).xcscheme | 2 +- openHAB/OpenHABRootViewController.swift | 11 +- openHAB/WatchMessageService.swift | 2 +- openHABWatch/Domain/UserData.swift | 114 +++++++------- openHABWatch/External/AppMessageService.swift | 6 +- .../Model/ObservableOpenHABDataObject.swift | 1 - .../Model/ObservableOpenHABWidget.swift | 2 +- openHABWatch/Views/ContentView.swift | 37 ++--- 16 files changed, 248 insertions(+), 149 deletions(-) diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift index 5f1b6a1a4..7b3504c56 100644 --- a/NotificationService/NotificationService.swift +++ b/NotificationService/NotificationService.swift @@ -152,7 +152,7 @@ class NotificationService: UNNotificationServiceExtension { url: Preferences.remoteUrl, priority: 1 ) - NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds) + NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds, ignoreSSLVerification: Preferences.ignoreSSL) NetworkTracker.shared.waitForActiveConnection { activeConnection in if let openHABUrl = activeConnection?.configuration.url, let uurl = URL(string: openHABUrl) { client.downloadFile(url: uurl.appendingPathComponent(url), completionHandler: downloadCompletionHandler) @@ -182,7 +182,7 @@ class NotificationService: UNNotificationServiceExtension { url: Preferences.remoteUrl, priority: 1 ) - NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds) + NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds, ignoreSSLVerification: Preferences.ignoreSSL) NetworkTracker.shared.waitForActiveConnection { activeConnection in if let openHABUrl = activeConnection?.configuration.url, let url = URL(string: openHABUrl) { client.getItem(baseURL: url, itemName: itemName) { item, error in diff --git a/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift b/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift index 983a4c1c0..087385521 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/HTTPClient.swift @@ -19,11 +19,17 @@ public class HTTPClient: NSObject { private let username: String private let password: String private let alwaysSendBasicAuth: Bool + private let ignoreSSL: Bool - public init(username: String, password: String, alwaysSendBasicAuth: Bool = false) { + // this can be changed if we detect another server + public var baseURL: URL? + + public init(baseURL: URL? = nil, username: String, password: String, alwaysSendBasicAuth: Bool = false, ignoreSSL: Bool = false) { + self.baseURL = baseURL self.username = username self.password = password self.alwaysSendBasicAuth = alwaysSendBasicAuth + self.ignoreSSL = ignoreSSL super.init() let config = URLSessionConfiguration.default @@ -44,7 +50,7 @@ public class HTTPClient: NSObject { - response: The URL response object providing response metadata, such as HTTP headers and status code. - error: An error object that indicates why the request failed, or `nil` if the request was successful. */ - public func doGet(baseURL: URL, path: String?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + public func doGet(baseURL: URL? = nil, path: String?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask? { doRequest(baseURL: baseURL, path: path, method: "GET") { result, response, error in let data = result as? Data completion(data, response, error) @@ -63,7 +69,7 @@ public class HTTPClient: NSObject { - response: The URL response object providing response metadata, such as HTTP headers and status code. - error: An error object that indicates why the request failed, or `nil` if the request was successful. */ - public func doPost(baseURL: URL, path: String?, body: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + public func doPost(baseURL: URL? = nil, path: String?, body: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask? { doRequest(baseURL: baseURL, path: path, method: "POST", body: body) { result, response, error in let data = result as? Data completion(data, response, error) @@ -82,7 +88,7 @@ public class HTTPClient: NSObject { - response: The URL response object providing response metadata, such as HTTP headers and status code. - error: An error object that indicates why the request failed, or `nil` if the request was successful. */ - public func doPut(baseURL: URL, path: String?, body: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + public func doPut(baseURL: URL? = nil, path: String?, body: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask? { doRequest(baseURL: baseURL, path: path, method: "PUT", body: body) { result, response, error in let data = result as? Data completion(data, response, error) @@ -99,8 +105,7 @@ public class HTTPClient: NSObject { - item: An `OpenHABItem` object returned by the server. This will be `nil` if the request fails. - error: An error object that indicates why the request failed, or `nil` if the request was successful. */ - public func getItem(baseURL: URL, itemName: String, completion: @escaping (OpenHABItem?, Error?) -> Void) { - os_log("getItem from URL %{public}@ and item %{public}@", log: .networking, type: .info, baseURL.absoluteString, itemName) + public func getItem(baseURL: URL? = nil, itemName: String, completion: @escaping (OpenHABItem?, Error?) -> Void) -> URLSessionTask? { doGet(baseURL: baseURL, path: "/rest/items/\(itemName)") { data, _, error in if let error { completion(nil, error) @@ -122,8 +127,7 @@ public class HTTPClient: NSObject { } } - public func getServerProperties(baseURL: URL, completion: @escaping (OpenHABServerProperties?, Error?) -> Void) { - os_log("getServerProperties from URL %{public}@", log: .networking, type: .info, baseURL.absoluteString) + public func getServerProperties(baseURL: URL? = nil, completion: @escaping (OpenHABServerProperties?, Error?) -> Void) -> URLSessionTask? { doGet(baseURL: baseURL, path: "/rest/") { data, _, error in if let error { completion(nil, error) @@ -156,53 +160,98 @@ public class HTTPClient: NSObject { - response: The URL response object providing response metadata, such as HTTP headers and status code. - error: An error object that indicates why the request failed, or `nil` if the request was successful. */ - public func downloadFile(url: URL, completionHandler: @escaping @Sendable (URL?, URLResponse?, (any Error)?) -> Void) { + public func downloadFile(url: URL, completionHandler: @escaping @Sendable (URL?, URLResponse?, (any Error)?) -> Void) -> URLSessionTask? { doRequest(baseURL: url, path: nil, method: "GET", download: true) { result, response, error in let fileURL = result as? URL completionHandler(fileURL, response, error) } } - // MARK: - Basic Authentication + public func sendCommand(url: URL? = nil, itemName: String, command: String, completion: @escaping (String?, Error?) -> Void) -> URLSessionTask? { + os_log("sendCommand %{public}@ %{public}@", log: .default, type: .debug, command, itemName) + return doPost(baseURL: url, path: "/rest/items/\(itemName)", body: command) { data, _, error in + if let error { + os_log("Could not send data %{public}@", log: .default, type: .error, error.localizedDescription) + completion(nil, error) + } else { + os_log("Request succeeded", log: .default, type: .info) + var returnValue = "" + if let data { + returnValue = String(data: data, encoding: .utf8) ?? "" + os_log("Data: %{public}@", log: .default, type: .debug, returnValue) + } + completion(returnValue, nil) + } + } + } - private func basicAuthHeader() -> String { - let authString = "\(username):\(password)" - let authData = authString.data(using: .utf8)! - return "Basic \(authData.base64EncodedString())" + public func loadSitemapData(url: URL? = nil, + longPolling: Bool, + refresh: Bool, + completion: @escaping (Data?, Error?) -> Void) -> URLSessionTask? { + let timeout: TimeInterval = longPolling ? 35.0 : 10.0 // for long polling, the server will return in 30 seconds + var headers: [String: String] = [:] + if longPolling { + headers["X-Atmosphere-Transport"] = "0" + } + + os_log("Fetching page from URL %{public}@", log: .networking, type: .info, url?.absoluteString ?? "") + + return doRequest(baseURL: url, path: nil, method: "GET", headers: headers, timeout: timeout) { result, _, error in + if let error { + os_log("error fetching page from URL %{public}@ %{public}@", log: .networking, type: .error, url?.absoluteString ?? "", error.localizedDescription) + completion(nil, error) + } else if let data = result as? Data { + os_log("Finsihed Fetching page from URL %{public}@", log: .networking, type: .info, url?.absoluteString ?? "") + completion(data, nil) + } else { + os_log("No data from URL %{public}@", log: .networking, type: .error, url?.absoluteString ?? "") + completion(nil, URLError(.unknown, userInfo: [NSLocalizedDescriptionKey: "No valid data received from server."])) + } + } } - private func doRequest(baseURL: URL, path: String?, method: String, body: String? = nil, download: Bool = false, completion: @escaping (Any?, URLResponse?, Error?) -> Void) { - var url = baseURL + public func doRequest(baseURL: URL?, path: String?, method: String, headers: [String: String]? = nil, + timeout: TimeInterval = 60.0, body: String? = nil, download: Bool = false, completion: @escaping (Any?, URLResponse?, Error?) -> Void) -> URLSessionTask? { + guard var url = baseURL ?? self.baseURL else { + os_log("doRequest ERROR: Base URL is nil", log: .networking, type: .info) + completion(nil, nil, NSError(domain: "HTTPClient", code: -1, userInfo: [NSLocalizedDescriptionKey: "Base URL is nil"])) + return nil + } + if let path { url.appendPathComponent(path) } - func sendRequest() { - var request = URLRequest(url: url) - request.httpMethod = method - if let body { - request.httpBody = body.data(using: .utf8) - request.setValue("text/plain", forHTTPHeaderField: "Content-Type") + var request = URLRequest(url: url) + request.httpMethod = method + request.timeoutInterval = timeout + if let headers { + for (key, value) in headers { + request.setValue(value, forHTTPHeaderField: key) } - performRequest(request: request, download: download) { result, response, error in - if let error { - os_log("Error with URL %{public}@ : %{public}@", log: .networking, type: .error, url.absoluteString, error.localizedDescription) - completion(nil, response, error) - } else if let response = response as? HTTPURLResponse { - if (400 ... 599).contains(response.statusCode) { - os_log("HTTP error from URL %{public}@ : %{public}d", log: .networking, type: .error, url.absoluteString, response.statusCode) - completion(nil, response, NSError(domain: "HTTPClient", code: response.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP error \(response.statusCode)"])) - } else { - os_log("Response from URL %{public}@ : %{public}d", log: .networking, type: .info, url.absoluteString, response.statusCode) - completion(result, response, nil) - } + } + if let body { + request.httpBody = body.data(using: .utf8) + request.setValue("text/plain", forHTTPHeaderField: "Content-Type") + } + return performRequest(request: request, download: download) { result, response, error in + if let error { + os_log("Error with URL %{public}@ : %{public}@", log: .networking, type: .error, url.absoluteString, error.localizedDescription) + completion(nil, response, error) + } else if let response = response as? HTTPURLResponse { + if (400 ... 599).contains(response.statusCode) { + os_log("HTTP error from URL %{public}@ : %{public}d", log: .networking, type: .error, url.absoluteString, response.statusCode) + completion(nil, response, NSError(domain: "HTTPClient", code: response.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP error \(response.statusCode)"])) + } else { + os_log("Response from URL %{public}@ : %{public}d", log: .networking, type: .info, url.absoluteString, response.statusCode) + completion(result, response, nil) } } } - sendRequest() } - private func performRequest(request: URLRequest, download: Bool, completion: @escaping (Any?, URLResponse?, Error?) -> Void) { + private func performRequest(request: URLRequest, download: Bool, completion: @escaping (Any?, URLResponse?, Error?) -> Void) -> URLSessionTask? { var request = request if alwaysSendBasicAuth { request.setValue(basicAuthHeader(), forHTTPHeaderField: "Authorization") @@ -218,6 +267,7 @@ public class HTTPClient: NSObject { } } task.resume() + return task } @available(watchOS 8.0, *) @@ -233,6 +283,14 @@ public class HTTPClient: NSObject { return try await session.data(for: request) } } + + // MARK: - Basic Authentication + + private func basicAuthHeader() -> String { + let authString = "\(username):\(password)" + let authData = authString.data(using: .utf8)! + return "Basic \(authData.base64EncodedString())" + } } extension HTTPClient: URLSessionDelegate, URLSessionTaskDelegate { @@ -271,11 +329,14 @@ extension HTTPClient: URLSessionDelegate, URLSessionTaskDelegate { } private func handleServerTrust(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { - guard let serverTrust = challenge.protectionSpace.serverTrust else { - return (.performDefaultHandling, nil) + if ignoreSSL { + os_log("Ignoring SSL certificate validation", log: .networking, type: .info) + if let serverTrust = challenge.protectionSpace.serverTrust { + let credential = URLCredential(trust: serverTrust) + return (.useCredential, credential) + } } - let credential = URLCredential(trust: serverTrust) - return (.useCredential, credential) + return (.performDefaultHandling, nil) } private func handleBasicAuth(challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { diff --git a/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift b/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift index 7f91145a9..dc8548a6e 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift @@ -23,7 +23,8 @@ public func onReceiveSessionTaskChallenge(with challenge: URLAuthenticationChall } else if challenge.protectionSpace.authenticationMethod.isAny(of: NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodDefault) { let localUrl = URL(string: Preferences.localUrl) let remoteUrl = URL(string: Preferences.remoteUrl) - if challenge.protectionSpace.host == localUrl?.host || challenge.protectionSpace.host == remoteUrl?.host || challenge.protectionSpace.host == "home.myopenhab.org" { + + if challenge.protectionSpace.host == localUrl?.host || challenge.protectionSpace.host == remoteUrl?.host { credential = URLCredential(user: Preferences.username, password: Preferences.password, persistence: .forSession) disposition = .useCredential os_log("HTTP BasicAuth host:'%{PUBLIC}@'", log: .default, type: .error, challenge.protectionSpace.host) @@ -191,7 +192,7 @@ public class NetworkConnection { } pageRequest.setValue(atmosphereTrackingId, forHTTPHeaderField: "X-Atmosphere-tracking-id") - os_log("OpenHABViewController sending new request", log: .remoteAccess, type: .error) + os_log("NetworkConnection: sending new request", log: .remoteAccess, type: .error) return NetworkConnection.shared.manager.request(pageRequest) .validate() diff --git a/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift b/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift index e6d0dd54a..aec90a3de 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift @@ -41,21 +41,21 @@ public final class NetworkTracker: ObservableObject { @Published public private(set) var activeConnection: ConnectionInfo? @Published public private(set) var status: NetworkStatus = .connecting + public private(set) var httpClient: HTTPClient? private let monitor: NWPathMonitor private let monitorQueue = DispatchQueue.global(qos: .background) private var priorityWorkItem: DispatchWorkItem? private var connectionConfigurations: [ConnectionConfiguration] = [] - private var httpClient: HTTPClient? private var retryTimer: DispatchSourceTimer? private let timerQueue = DispatchQueue(label: "com.openhab.networktracker.timerQueue") - private let connectedRetryInterval: TimeInterval = 60 // amount of time we scan for better connections when connected private let disconnectedRetryInterval: TimeInterval = 30 // amount of time we scan when not connected private init() { monitor = NWPathMonitor() monitor.pathUpdateHandler = { [weak self] path in + guard self?.httpClient != nil else { return } if path.status == .satisfied { os_log("Network status: Connected", log: OSLog.default, type: .info) self?.checkActiveConnection() @@ -68,9 +68,10 @@ public final class NetworkTracker: ObservableObject { monitor.start(queue: monitorQueue) } - public func startTracking(connectionConfigurations: [ConnectionConfiguration], username: String, password: String, alwaysSendBasicAuth: Bool) { + public func startTracking(connectionConfigurations: [ConnectionConfiguration], username: String, password: String, alwaysSendBasicAuth: Bool, ignoreSSLVerification: Bool) { + os_log("NetworkConnection: startTracking", log: OSLog.default, type: .info) self.connectionConfigurations = connectionConfigurations - httpClient = HTTPClient(username: username, password: password, alwaysSendBasicAuth: alwaysSendBasicAuth) + httpClient = HTTPClient(username: username, password: password, alwaysSendBasicAuth: alwaysSendBasicAuth, ignoreSSL: ignoreSSLVerification) setActiveConnection(nil) attemptConnection() } @@ -78,7 +79,9 @@ public final class NetworkTracker: ObservableObject { public func waitForActiveConnection( perform action: @escaping (ConnectionInfo?) -> Void ) -> AnyCancellable { - $activeConnection + os_log("NetworkConnection: waitForActiveConnection", log: OSLog.default, type: .info) + + return $activeConnection .filter { $0 != nil } // Only proceed if activeConnection is not nil .first() // Automatically cancels after the first non-nil value .receive(on: DispatchQueue.main) @@ -91,12 +94,15 @@ public final class NetworkTracker: ObservableObject { private func checkActiveConnection() { guard let activeConnection else { // No active connection, proceed with the normal connection attempt + os_log("NetworkConnection: checkActiveConnection attemptConnection", log: OSLog.default, type: .info) attemptConnection() return } // Check if the active connection is reachable if let url = URL(string: activeConnection.configuration.url) { + os_log("checkActiveConnection trying %{PUBLIC}@", log: OSLog.default, type: .info, url.absoluteString) + httpClient?.getServerProperties(baseURL: url) { [weak self] _, error in if let error { os_log("Network status: Active connection is not reachable: %{PUBLIC}@ %{PUBLIC}@", log: OSLog.default, type: .error, activeConnection.configuration.url, error.localizedDescription) @@ -141,6 +147,7 @@ public final class NetworkTracker: ObservableObject { for configuration in connectionConfigurations { dispatchGroup.enter() checkOutstanding = true // Signal that checks are outstanding + os_log("attemptConnection trying %{PUBLIC}@", log: OSLog.default, type: .info, configuration.url) if let url = URL(string: configuration.url) { httpClient?.getServerProperties(baseURL: url) { [weak self] props, error in guard let self else { return } @@ -228,7 +235,8 @@ public final class NetworkTracker: ObservableObject { activeConnection = connection if activeConnection != nil { updateStatus(.connected) - startRetryTimer(connectedRetryInterval) + httpClient?.baseURL = URL(string: activeConnection!.configuration.url) + // startRetryTimer(connectedRetryInterval) } else { updateStatus(.notConnected) startRetryTimer(disconnectedRetryInterval) diff --git a/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift b/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift index 6a33671fe..6cc686340 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/OpenHABItemCache.swift @@ -32,7 +32,7 @@ public class OpenHABItemCache { url: Preferences.remoteUrl, priority: 1 ) - NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds) + NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: Preferences.username, password: Preferences.password, alwaysSendBasicAuth: Preferences.alwaysSendCreds, ignoreSSLVerification: Preferences.ignoreSSL) } public func getItemNames(searchTerm: String?, types: [OpenHABItem.ItemType]?, completion: @escaping ([NSString]) -> Void) { diff --git a/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift b/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift index 371f39da7..b070e3c87 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift @@ -55,17 +55,16 @@ public struct UserDefaultURL { get { let storedValue = Preferences.sharedDefaults.string(forKey: key) ?? defaultValue let trimmedUri = uriWithoutTrailingSlashes(storedValue).trimmingCharacters(in: .whitespacesAndNewlines) - return trimmedUri.isValidURL ? trimmedUri : defaultValue + return adjustURLIfNeeded(for: key, url: storedValue) } set { - Preferences.sharedDefaults.set(newValue, forKey: key) + let formattedValue = adjustURLIfNeeded(for: key, url: newValue) + Preferences.sharedDefaults.set(formattedValue, forKey: key) let subject = subject let defaultValue = defaultValue - // Trim and validate the new URL - let trimmedUri = uriWithoutTrailingSlashes(newValue).trimmingCharacters(in: .whitespacesAndNewlines) DispatchQueue.main.async { - if trimmedUri.isValidURL { - subject.send(trimmedUri) + if formattedValue.isValidURL { + subject.send(formattedValue) } else { subject.send(defaultValue) } @@ -90,6 +89,28 @@ public struct UserDefaultURL { } return hostUri } + + private func adjustURLIfNeeded(for key: String, url: String) -> String { + // Step 1: Remove trailing slashes + let trimmedUrl = uriWithoutTrailingSlashes(url).trimmingCharacters(in: .whitespacesAndNewlines) + + // Step 2: Apply special logic only for "remoteUrl" + if key == "remoteUrl" { + guard let urlComponents = URLComponents(string: trimmedUrl), + let host = urlComponents.host else { + return defaultValue + } + + // Adjust the host if it contains "myopenhab.org" but isn't "home.myopenhab.org" + if host.contains("myopenhab.org"), host != "home.myopenhab.org" { + var newComponents = urlComponents + newComponents.host = "home.myopenhab.org" + return newComponents.url?.absoluteString ?? defaultValue + } + } + + return trimmedUrl + } } public enum Preferences { diff --git a/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme b/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme index dd9c28004..45cfdeb6b 100644 --- a/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme +++ b/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme @@ -1,6 +1,6 @@ + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + debugDocumentVersioning = "YES" + notificationPayloadFile = "openHABWatch/Extension/PushNotificationPayload.apns"> () private let logger = Logger(subsystem: "org.openhab.app.watchkitapp", category: "UserData") @@ -63,17 +62,17 @@ final class UserData: ObservableObject { widgets = openHABSitemapPage?.widgets ?? [] openHABSitemapPage?.sendCommand = { [weak self] item, command in - self?.sendCommand(item, commandToSend: command) + self?.sendCommand(item, command: command) } } init(sitemapName: String = "watch") { - updateNetworkTracker() + updateNetwork() NetworkTracker.shared.$activeConnection .receive(on: DispatchQueue.main) .sink { [weak self] activeConnection in if let activeConnection { - self?.logger.error("openHABTracked: \(activeConnection.configuration.url)") + self?.logger.info("openHABTracked: \(activeConnection.configuration.url)") if !ObservableOpenHABDataObject.shared.haveReceivedAppContext { AppMessageService.singleton.requestApplicationContext() @@ -94,12 +93,12 @@ final class UserData: ObservableObject { ObservableOpenHABDataObject.shared.objectRefreshed.sink { _ in // New settings updates from the phone app to start a reconnect self.logger.info("Settings update received, starting reconnect") - self.updateNetworkTracker() + self.updateNetwork() } .store(in: &cancellables) } - func updateNetworkTracker() { + func updateNetwork() { if !ObservableOpenHABDataObject.shared.localUrl.isEmpty || !ObservableOpenHABDataObject.shared.remoteUrl.isEmpty { let connection1 = ConnectionConfiguration( url: ObservableOpenHABDataObject.shared.localUrl, @@ -109,69 +108,80 @@ final class UserData: ObservableObject { url: ObservableOpenHABDataObject.shared.remoteUrl, priority: 1 ) - NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: ObservableOpenHABDataObject.shared.openHABUsername, password: ObservableOpenHABDataObject.shared.openHABPassword, alwaysSendBasicAuth: ObservableOpenHABDataObject.shared.openHABAlwaysSendCreds) + NetworkTracker.shared.startTracking(connectionConfigurations: [connection1, connection2], username: ObservableOpenHABDataObject.shared.openHABUsername, password: ObservableOpenHABDataObject.shared.openHABPassword, alwaysSendBasicAuth: ObservableOpenHABDataObject.shared.openHABAlwaysSendCreds, ignoreSSLVerification: ObservableOpenHABDataObject.shared.ignoreSSL) } } - func loadPage(url: URL?, - longPolling: Bool, - refresh: Bool) { - if currentPageOperation != nil { - currentPageOperation?.cancel() - currentPageOperation = nil + func loadPage(url: URL? = nil, longPolling: Bool, refresh: Bool) { + logger.info("Loading page \(url?.absoluteString ?? "") longPolling \(longPolling) refresh \(refresh)") + + // Cancel any running operation + if let currentPageOperation, currentPageOperation.state == .running { + currentPageOperation.cancel() } - currentPageOperation = NetworkConnection.page( - url: url, - longPolling: longPolling - ) { [weak self] response in + currentPageOperation = NetworkTracker.shared.httpClient?.loadSitemapData(url: url, longPolling: longPolling, refresh: refresh) { [weak self] data, error in guard let self else { return } + currentPageOperation = nil + + if let error = error as? URLError, error.code == .cancelled { + logger.info("Task was canceled") + return + } + + var errorString: String? + + if error != nil || data == nil { + errorString = error?.localizedDescription ?? "No data received" + } - switch response.result { - case let .success(data): - logger.info("Page loaded with success") + if errorString == nil { do { - // Self-executing closure - // Inspired by https://www.swiftbysundell.com/posts/inline-types-and-functions-in-swift - openHABSitemapPage = try { - let sitemapPageCodingData = try data.decoded(as: ObservableOpenHABSitemapPage.CodingData.self) - return sitemapPageCodingData.openHABSitemapPage - }() + let sitemapPageCodingData = try data!.decoded(as: ObservableOpenHABSitemapPage.CodingData.self) + openHABSitemapPage = sitemapPageCodingData.openHABSitemapPage } catch { - logger.error("Should not throw \(error.localizedDescription)") + logger.error("Decoding error: \(error.localizedDescription)") + errorString = error.localizedDescription } + } - openHABSitemapPage?.sendCommand = { [weak self] item, command in - self?.sendCommand(item, commandToSend: command) + if let errorString { + DispatchQueue.main.async { + self.logger.error("On LoadPage \"\(errorString)\"") + self.errorDescription = errorString + self.widgets = [] + self.showAlert = true } + return + } - widgets = openHABSitemapPage?.widgets ?? [] - - showAlert = widgets.isEmpty ? true : false - if refresh { loadPage( - url: url, - longPolling: true, - refresh: true - ) } + // Configures then sendCommand closure (existing logic) + openHABSitemapPage?.sendCommand = { [weak self] item, command in + self?.sendCommand(item, command: command) + } - case let .failure(error): - logger.error("On LoadPage \"\(error.localizedDescription)\" with code: \(response.response?.statusCode ?? 0)") - errorDescription = error.localizedDescription - widgets = [] - showAlert = true + // Always update UI on the main thread + DispatchQueue.main.async { + self.widgets = self.openHABSitemapPage?.widgets ?? [] + self.showAlert = self.widgets.isEmpty + if refresh { + self.loadPage(url: url, longPolling: true, refresh: true) + } } } - currentPageOperation?.resume() } - func sendCommand(_ item: OpenHABItem?, commandToSend command: String?) { - if commandOperation != nil { - commandOperation?.cancel() - commandOperation = nil + func sendCommand(_ item: OpenHABItem?, command: String?) { + if let commandOperation, commandOperation.state == .running { + commandOperation.cancel() } if let item, let command { - commandOperation = NetworkConnection.sendCommand(item: item, commandToSend: command) - commandOperation?.resume() + commandOperation = NetworkTracker.shared.httpClient?.sendCommand(itemName: item.name, command: command) { _, error in + if error != nil { + self.logger.error("Error sending command \(command) to \(item.name): \(error!.localizedDescription)") + } + self.commandOperation = nil + } } } diff --git a/openHABWatch/External/AppMessageService.swift b/openHABWatch/External/AppMessageService.swift index 83e1657d4..dfb6f5c45 100644 --- a/openHABWatch/External/AppMessageService.swift +++ b/openHABWatch/External/AppMessageService.swift @@ -22,9 +22,6 @@ class AppMessageService: NSObject, WCSessionDelegate { private let logger = Logger(subsystem: "org.openhab.app.watchkitapp", category: "AppMessageService") func updateValuesFromApplicationContext(_ applicationContext: [String: AnyObject]) { - if NetworkConnection.shared == nil { - NetworkConnection.initialize(ignoreSSL: Preferences.ignoreSSL, interceptor: nil) - } if !applicationContext.isEmpty { if let localUrl = applicationContext["localUrl"] as? String { ObservableOpenHABDataObject.shared.localUrl = localUrl @@ -55,8 +52,7 @@ class AppMessageService: NSObject, WCSessionDelegate { } if let trustedCertificates = applicationContext["trustedCertificates"] as? [String: Data] { - NetworkConnection.shared.serverCertificateManager.trustedCertificates = trustedCertificates - NetworkConnection.shared.serverCertificateManager.saveTrustedCertificates() + // do we need to do anything here? We load from the shared keychain. } if let alwaysSendCreds = applicationContext["alwaysSendCreds"] as? Bool { diff --git a/openHABWatch/Model/ObservableOpenHABDataObject.swift b/openHABWatch/Model/ObservableOpenHABDataObject.swift index 86d678c01..3fd47549d 100644 --- a/openHABWatch/Model/ObservableOpenHABDataObject.swift +++ b/openHABWatch/Model/ObservableOpenHABDataObject.swift @@ -74,7 +74,6 @@ final class ObservableOpenHABDataObject: DataObject, ObservableObject { var ignoreSSL: Bool { willSet { objectWillChange.send() - NetworkConnection.shared.serverCertificateManager.ignoreSSL = newValue } } diff --git a/openHABWatch/Model/ObservableOpenHABWidget.swift b/openHABWatch/Model/ObservableOpenHABWidget.swift index b8ec2b7c1..530e1bc14 100644 --- a/openHABWatch/Model/ObservableOpenHABWidget.swift +++ b/openHABWatch/Model/ObservableOpenHABWidget.swift @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: EPL-2.0 -import Alamofire +// import Alamofire #if canImport(Combine) import Combine #endif diff --git a/openHABWatch/Views/ContentView.swift b/openHABWatch/Views/ContentView.swift index 4f89d89ea..1155767c0 100644 --- a/openHABWatch/Views/ContentView.swift +++ b/openHABWatch/Views/ContentView.swift @@ -22,7 +22,7 @@ struct ContentView: View { ZStack { ScrollView { HStack { - Text(viewModel.openHABSitemapPage?.title ?? "Sitemap without title") + Text(viewModel.openHABSitemapPage?.title ?? "Waiting...") .font(.body) .lineLimit(1) Spacer() @@ -32,23 +32,24 @@ struct ContentView: View { } } .navigationBarTitle(Text(title)) - .actionSheet(isPresented: $viewModel.showCertificateAlert) { - ActionSheet( - title: Text(NSLocalizedString("warning", comment: "")), - message: Text(viewModel.certificateErrorDescription), - buttons: [ - .default(Text(NSLocalizedString("abort", comment: ""))) { - NetworkConnection.shared.serverCertificateManager.evaluateResult = .deny - }, - .default(Text(NSLocalizedString("once", comment: ""))) { - NetworkConnection.shared.serverCertificateManager.evaluateResult = .permitOnce - }, - .default(Text(NSLocalizedString("always", comment: ""))) { - NetworkConnection.shared.serverCertificateManager.evaluateResult = .permitAlways - } - ] - ) - } + // Appearently this was never implemented +// .actionSheet(isPresented: $viewModel.showCertificateAlert) { +// ActionSheet( +// title: Text(NSLocalizedString("warning", comment: "")), +// message: Text(viewModel.certificateErrorDescription), +// buttons: [ +// .default(Text(NSLocalizedString("abort", comment: ""))) { +// NetworkConnection.shared.serverCertificateManager.evaluateResult = .deny +// }, +// .default(Text(NSLocalizedString("once", comment: ""))) { +// NetworkConnection.shared.serverCertificateManager.evaluateResult = .permitOnce +// }, +// .default(Text(NSLocalizedString("always", comment: ""))) { +// NetworkConnection.shared.serverCertificateManager.evaluateResult = .permitAlways +// } +// ] +// ) +// } if viewModel.showAlert { Text("Refreshing...") .onAppear { From bc297aa918303d22a32a40955a87e88dd2b786f1 Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Mon, 13 Jan 2025 08:50:35 -0800 Subject: [PATCH 02/10] Clean up a bit Signed-off-by: Dan Cunningham --- BuildTools/Empty.swift | 1 - .../OpenHABCore/Util/NetworkConnection.swift | 5 ++- .../OpenHABCore/Util/NetworkTracker.swift | 18 +++++++++- .../OpenHABCore/Util/Preferences.swift | 33 ++++--------------- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/BuildTools/Empty.swift b/BuildTools/Empty.swift index 8139d407d..f15a45c86 100644 --- a/BuildTools/Empty.swift +++ b/BuildTools/Empty.swift @@ -8,4 +8,3 @@ // http://www.eclipse.org/legal/epl-2.0 // // SPDX-License-Identifier: EPL-2.0 - diff --git a/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift b/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift index dc8548a6e..7f91145a9 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/NetworkConnection.swift @@ -23,8 +23,7 @@ public func onReceiveSessionTaskChallenge(with challenge: URLAuthenticationChall } else if challenge.protectionSpace.authenticationMethod.isAny(of: NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodDefault) { let localUrl = URL(string: Preferences.localUrl) let remoteUrl = URL(string: Preferences.remoteUrl) - - if challenge.protectionSpace.host == localUrl?.host || challenge.protectionSpace.host == remoteUrl?.host { + if challenge.protectionSpace.host == localUrl?.host || challenge.protectionSpace.host == remoteUrl?.host || challenge.protectionSpace.host == "home.myopenhab.org" { credential = URLCredential(user: Preferences.username, password: Preferences.password, persistence: .forSession) disposition = .useCredential os_log("HTTP BasicAuth host:'%{PUBLIC}@'", log: .default, type: .error, challenge.protectionSpace.host) @@ -192,7 +191,7 @@ public class NetworkConnection { } pageRequest.setValue(atmosphereTrackingId, forHTTPHeaderField: "X-Atmosphere-tracking-id") - os_log("NetworkConnection: sending new request", log: .remoteAccess, type: .error) + os_log("OpenHABViewController sending new request", log: .remoteAccess, type: .error) return NetworkConnection.shared.manager.request(pageRequest) .validate() diff --git a/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift b/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift index aec90a3de..500e7b0c9 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/NetworkTracker.swift @@ -70,7 +70,7 @@ public final class NetworkTracker: ObservableObject { public func startTracking(connectionConfigurations: [ConnectionConfiguration], username: String, password: String, alwaysSendBasicAuth: Bool, ignoreSSLVerification: Bool) { os_log("NetworkConnection: startTracking", log: OSLog.default, type: .info) - self.connectionConfigurations = connectionConfigurations + self.connectionConfigurations = adjustMyOpenHABHosts(in: connectionConfigurations) httpClient = HTTPClient(username: username, password: password, alwaysSendBasicAuth: alwaysSendBasicAuth, ignoreSSL: ignoreSSLVerification) setActiveConnection(nil) attemptConnection() @@ -248,4 +248,20 @@ public final class NetworkTracker: ObservableObject { status = newStatus } } + + private func adjustMyOpenHABHosts(in configurations: [ConnectionConfiguration]) -> [ConnectionConfiguration] { + configurations.map { configuration in + let updatedURL: String + if let urlComponents = URLComponents(string: configuration.url), + let host = urlComponents.host, + host.contains("myopenhab.org"), host != "home.myopenhab.org" { + var newComponents = urlComponents + newComponents.host = "home.myopenhab.org" + updatedURL = newComponents.url?.absoluteString ?? configuration.url + } else { + updatedURL = configuration.url + } + return ConnectionConfiguration(url: updatedURL, priority: configuration.priority) + } + } } diff --git a/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift b/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift index b070e3c87..371f39da7 100644 --- a/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift +++ b/OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift @@ -55,16 +55,17 @@ public struct UserDefaultURL { get { let storedValue = Preferences.sharedDefaults.string(forKey: key) ?? defaultValue let trimmedUri = uriWithoutTrailingSlashes(storedValue).trimmingCharacters(in: .whitespacesAndNewlines) - return adjustURLIfNeeded(for: key, url: storedValue) + return trimmedUri.isValidURL ? trimmedUri : defaultValue } set { - let formattedValue = adjustURLIfNeeded(for: key, url: newValue) - Preferences.sharedDefaults.set(formattedValue, forKey: key) + Preferences.sharedDefaults.set(newValue, forKey: key) let subject = subject let defaultValue = defaultValue + // Trim and validate the new URL + let trimmedUri = uriWithoutTrailingSlashes(newValue).trimmingCharacters(in: .whitespacesAndNewlines) DispatchQueue.main.async { - if formattedValue.isValidURL { - subject.send(formattedValue) + if trimmedUri.isValidURL { + subject.send(trimmedUri) } else { subject.send(defaultValue) } @@ -89,28 +90,6 @@ public struct UserDefaultURL { } return hostUri } - - private func adjustURLIfNeeded(for key: String, url: String) -> String { - // Step 1: Remove trailing slashes - let trimmedUrl = uriWithoutTrailingSlashes(url).trimmingCharacters(in: .whitespacesAndNewlines) - - // Step 2: Apply special logic only for "remoteUrl" - if key == "remoteUrl" { - guard let urlComponents = URLComponents(string: trimmedUrl), - let host = urlComponents.host else { - return defaultValue - } - - // Adjust the host if it contains "myopenhab.org" but isn't "home.myopenhab.org" - if host.contains("myopenhab.org"), host != "home.myopenhab.org" { - var newComponents = urlComponents - newComponents.host = "home.myopenhab.org" - return newComponents.url?.absoluteString ?? defaultValue - } - } - - return trimmedUrl - } } public enum Preferences { From acf6ff90d7ca5901e846a83ecd8de0987c241e7b Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Wed, 22 Jan 2025 07:42:12 -0800 Subject: [PATCH 03/10] support actions when launching app Signed-off-by: Dan Cunningham --- openHAB/AppDelegate.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openHAB/AppDelegate.swift b/openHAB/AppDelegate.swift index 72e6ecaab..a88e497b1 100644 --- a/openHAB/AppDelegate.swift +++ b/openHAB/AppDelegate.swift @@ -140,6 +140,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD return clientCertificateManager.startImportClientCertificate(url: url) } + // remove the 'openhab' from the url + let action = url.absoluteString.split(separator: ":").dropFirst().joined(separator: ":") + notifyNotificationListeners(["actionIdentifier": action]) return true } From d28d7ec653e0a91f5497ef8671f5f240f2555637 Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Wed, 22 Jan 2025 07:47:55 -0800 Subject: [PATCH 04/10] Reset xcode scheme Signed-off-by: Dan Cunningham --- .../openHABWatch (Notification).xcscheme | 2 +- .../xcshareddata/xcschemes/openHABWatch.xcscheme | 15 +++++++-------- .../openHABWatchSwift (Complication).xcscheme | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme b/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme index 45cfdeb6b..dd9c28004 100644 --- a/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme +++ b/openHAB.xcodeproj/xcshareddata/xcschemes/openHABWatch (Notification).xcscheme @@ -1,6 +1,6 @@ + buildForTesting = "NO" + buildForRunning = "NO" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "NO"> + debugDocumentVersioning = "YES"> Date: Wed, 22 Jan 2025 07:49:36 -0800 Subject: [PATCH 05/10] small formatting tweaks Signed-off-by: Dan Cunningham --- openHABWatch/Domain/UserData.swift | 1 - openHABWatch/Model/ObservableOpenHABWidget.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/openHABWatch/Domain/UserData.swift b/openHABWatch/Domain/UserData.swift index ee5595421..b684b3477 100644 --- a/openHABWatch/Domain/UserData.swift +++ b/openHABWatch/Domain/UserData.swift @@ -9,7 +9,6 @@ // // SPDX-License-Identifier: EPL-2.0 -// import Alamofire import Combine import Foundation import OpenHABCore diff --git a/openHABWatch/Model/ObservableOpenHABWidget.swift b/openHABWatch/Model/ObservableOpenHABWidget.swift index 530e1bc14..4d94dff24 100644 --- a/openHABWatch/Model/ObservableOpenHABWidget.swift +++ b/openHABWatch/Model/ObservableOpenHABWidget.swift @@ -9,7 +9,6 @@ // // SPDX-License-Identifier: EPL-2.0 -// import Alamofire #if canImport(Combine) import Combine #endif From 9a13a641dcf4fcda51b443b10fe091bc8369ade2 Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Fri, 24 Jan 2025 19:37:29 +0100 Subject: [PATCH 06/10] Updated fastlane and device for fastlane test Signed-off-by: Tim Bert <5411131+timbms@users.noreply.github.com> --- Gemfile.lock | 48 +++++++++++++++++++++++------------------------ fastlane/Fastfile | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f4d4f3f95..3f33e08bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,20 +10,20 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.998.0) - aws-sdk-core (3.211.0) + aws-partitions (1.1042.0) + aws-sdk-core (3.216.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.95.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-kms (1.97.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.169.0) - aws-sdk-core (~> 3, >= 3.210.0) + aws-sdk-s3 (1.178.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sigv4 (1.10.1) + aws-sigv4 (1.11.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) @@ -58,8 +58,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -67,8 +67,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.1) faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.225.0) + fastimage (2.4.0) + fastlane (2.226.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -108,7 +108,7 @@ GEM tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) + xcpretty (~> 0.4.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-changelog (0.16.0) fastlane-plugin-versioning (0.6.0) @@ -152,12 +152,12 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.7) + http-cookie (1.0.8) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.7.5) - jwt (2.9.3) + json (2.9.1) + jwt (2.10.1) base64 mini_magick (4.13.2) mini_mime (1.1.5) @@ -166,9 +166,9 @@ GEM nanaimo (0.4.0) naturally (2.2.1) nkf (0.2.0) - optparse (0.5.0) + optparse (0.6.0) os (1.1.4) - plist (3.7.1) + plist (3.7.2) public_suffix (6.0.1) rake (13.2.1) representable (3.2.0) @@ -176,10 +176,10 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.9) - rouge (2.0.7) + rexml (3.4.0) + rouge (3.28.0) ruby2_keywords (0.0.5) - rubyzip (2.3.2) + rubyzip (2.4.1) security (0.1.5) signet (0.19.0) addressable (~> 2.8) @@ -201,15 +201,15 @@ GEM uber (0.1.0) unicode-display_width (2.6.0) word_wrap (1.0.0) - xcodeproj (1.26.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.4.0) rexml (>= 3.3.6, < 4.0) - xcpretty (0.3.0) - rouge (~> 2.0.7) + xcpretty (0.4.0) + rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) @@ -222,4 +222,4 @@ DEPENDENCIES fastlane-plugin-versioning BUNDLED WITH - 2.5.22 + 2.6.2 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5d2a6b5a6..2022695e5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -57,7 +57,7 @@ platform :ios do scheme: 'openHABTestsSwift', xcargs: '-skipPackagePluginValidation', testplan: 'openHABTests', - devices: ['iPhone 15 Pro'], + devices: ['iPhone 15 Pro (17.0)'], clean: true ) end From 7ac5710704311cdd51e158549494644a47c53ab7 Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:19:25 +0100 Subject: [PATCH 07/10] macos-latest and iPhone 16 Pro Signed-off-by: Tim Bert <5411131+timbms@users.noreply.github.com> --- .github/workflows/pull_requests.yml | 2 +- fastlane/Fastfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 706941d48..4e593028c 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -7,7 +7,7 @@ on: jobs: build_and_test: - runs-on: macos-14 + runs-on: macos-latest if: ${{ github.event.pull_request.draft == false }} steps: - uses: maxim-lobanov/setup-xcode@v1 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2022695e5..c627a95c6 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -57,7 +57,7 @@ platform :ios do scheme: 'openHABTestsSwift', xcargs: '-skipPackagePluginValidation', testplan: 'openHABTests', - devices: ['iPhone 15 Pro (17.0)'], + devices: ['iPhone 16 Pro'], clean: true ) end From bb22ef91969893c759b7971e779d52d540d87a8c Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:48:18 +0100 Subject: [PATCH 08/10] Update fastlane plugins Signed-off-by: Tim Bert <5411131+timbms@users.noreply.github.com> --- .github/workflows/pull_requests.yml | 2 +- Gemfile.lock | 2 +- fastlane/Fastfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index 4e593028c..db580a207 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -10,7 +10,7 @@ jobs: runs-on: macos-latest if: ${{ github.event.pull_request.draft == false }} steps: - - uses: maxim-lobanov/setup-xcode@v1 + - uses: maxim-lobanov/setup-xcode@v1.6.0 with: xcode-version: latest-stable diff --git a/Gemfile.lock b/Gemfile.lock index 3f33e08bc..9ed3e3103 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -111,7 +111,7 @@ GEM xcpretty (~> 0.4.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-changelog (0.16.0) - fastlane-plugin-versioning (0.6.0) + fastlane-plugin-versioning (0.7.1) fastlane-sirp (1.0.0) sysrandom (~> 1.0) gh_inspector (1.1.3) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c627a95c6..2022695e5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -57,7 +57,7 @@ platform :ios do scheme: 'openHABTestsSwift', xcargs: '-skipPackagePluginValidation', testplan: 'openHABTests', - devices: ['iPhone 16 Pro'], + devices: ['iPhone 15 Pro (17.0)'], clean: true ) end From 552efee19d1cf3e1142493c4e08d606f97ab1d5c Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:05:59 +0100 Subject: [PATCH 09/10] macos-15 Signed-off-by: Tim Bert <5411131+timbms@users.noreply.github.com> --- .github/workflows/pull_requests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_requests.yml b/.github/workflows/pull_requests.yml index db580a207..3fa86d31f 100644 --- a/.github/workflows/pull_requests.yml +++ b/.github/workflows/pull_requests.yml @@ -7,7 +7,7 @@ on: jobs: build_and_test: - runs-on: macos-latest + runs-on: macos-15 if: ${{ github.event.pull_request.draft == false }} steps: - uses: maxim-lobanov/setup-xcode@v1.6.0 From 90e6f69e1a7bb49250206ebc56e4ed585577b483 Mon Sep 17 00:00:00 2001 From: Tim Bert <5411131+timbms@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:13:01 +0100 Subject: [PATCH 10/10] iOS 17.5 Signed-off-by: Tim Bert <5411131+timbms@users.noreply.github.com> --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 2022695e5..f016e49b8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -57,7 +57,7 @@ platform :ios do scheme: 'openHABTestsSwift', xcargs: '-skipPackagePluginValidation', testplan: 'openHABTests', - devices: ['iPhone 15 Pro (17.0)'], + devices: ['iPhone 15 Pro (17.5)'], clean: true ) end