From 060bb9d181447e2538e5883ce647bca758b89df1 Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Mon, 16 Mar 2026 08:54:34 -0500 Subject: [PATCH 1/8] JPCFM-5534 story/JPCFM-5534 Add wrappers around dismissView to stop app freezing --- Source/SwiftUI/UploadInfoView.swift | 17 +++++++++++------ .../TCCProfileViewController.swift | 14 +++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 18cdc98..bdddc11 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -12,11 +12,13 @@ struct UploadInfoView: View { /// The signing identities available to be used. let signingIdentities: [SigningIdentity] /// Function to call when this view needs to be removed - let dismissAction: (() -> Void)? + let dismissAction: () -> Void // Communicate this info to the user @State private var warningInfo: String? @State private var networkOperationInfo: String? + /// Tracks whether the view has been dismissed to guard async callbacks + @State private var isDismissed = false /// Must sign the profile if Jamf Pro is less than v10.7.1 @State private var mustSign = false /// The hash of connection info that has been verified with a succesful connection @@ -280,6 +282,8 @@ struct UploadInfoView: View { let uploadMgr = UploadManager(serverURL: serverURL) uploadMgr.verifyConnection(authManager: makeAuthManager()) { result in + guard !isDismissed else { return } + if case .success(let success) = result { mustSign = success.mustSign organization = success.organization @@ -305,13 +309,13 @@ struct UploadInfoView: View { } private func dismissView() { + isDismissed = true + if !saveToKeychain { try? SecurityWrapper.removeCredentials(server: serverURL, username: username) } - if let dismiss = dismissAction { - dismiss() - } + dismissAction() } func performUpload() { @@ -342,6 +346,8 @@ struct UploadInfoView: View { authMgr: makeAuthManager(), siteInfo: siteIdAndName, signingIdentity: mustSign ? signingId : nil) { possibleError in + guard !isDismissed else { return } + if let error = possibleError { warningInfo = error.localizedDescription } else { @@ -354,6 +360,5 @@ struct UploadInfoView: View { } #Preview { - UploadInfoView(signingIdentities: [], - dismissAction: nil) + UploadInfoView(signingIdentities: [], dismissAction: {}) } diff --git a/Source/View Controllers/TCCProfileViewController.swift b/Source/View Controllers/TCCProfileViewController.swift index 835439a..5628215 100644 --- a/Source/View Controllers/TCCProfileViewController.swift +++ b/Source/View Controllers/TCCProfileViewController.swift @@ -24,7 +24,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // -// swiftlint:disable file_length import Cocoa import OSLog @@ -188,13 +187,14 @@ class TCCProfileViewController: NSViewController { logger.error("Error loading identities: \(error.localizedDescription)") } - let uploadView = UploadInfoView(signingIdentities: identities) { - // Dismiss the sheet when the UploadInfoView decides it is done - if let controller = self.presentedViewControllers?.first { - self.dismiss(controller) - } + let hostingController = NSHostingController( + rootView: UploadInfoView(signingIdentities: identities, dismissAction: {}) + ) + hostingController.rootView = UploadInfoView(signingIdentities: identities) { [weak self, weak hostingController] in + guard let self, let controller = hostingController else { return } + self.dismiss(controller) } - self.presentAsSheet(NSHostingController(rootView: uploadView)) + self.presentAsSheet(hostingController) } fileprivate func showAlert(_ error: LocalizedError, for window: NSWindow) { From ca4cadd8fbed04c12a579da865eb463e73bdb144 Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Mon, 16 Mar 2026 09:50:02 -0500 Subject: [PATCH 2/8] JPCFM-5534 story/JPCFM-5534 Fix trailing closure and function body length warnings --- PPPC UtilityTests/ModelTests/ModelTests.swift | 10 ++++------ Source/SwiftUI/UploadInfoView.swift | 2 +- Source/View Controllers/TCCProfileViewController.swift | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/PPPC UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index a07cb54..0e1a57e 100644 --- a/PPPC UtilityTests/ModelTests/ModelTests.swift +++ b/PPPC UtilityTests/ModelTests/ModelTests.swift @@ -218,9 +218,8 @@ class ModelTests: XCTestCase { // then verify the services XCTAssertEqual(2, content.services.count) - let appleEvents = content.services["AppleEvents"] - XCTAssertNotNil(appleEvents) - let appleEventsPolicy = appleEvents?.first + let appleEventsPolicy = content.services["AppleEvents"]?.first + XCTAssertNotNil(appleEventsPolicy) XCTAssertEqual("one", appleEventsPolicy?.identifier) XCTAssertEqual("oneReq", appleEventsPolicy?.codeRequirement) XCTAssertEqual("bundleID", appleEventsPolicy?.identifierType) @@ -230,9 +229,8 @@ class ModelTests: XCTestCase { XCTAssertTrue(appleEventsPolicy?.allowed == true) XCTAssertNil(appleEventsPolicy?.authorization) - let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertNotNil(allFiles) - let allFilesPolicy = allFiles?.first + let allFilesPolicy = content.services["SystemPolicyAllFiles"]?.first + XCTAssertNotNil(allFilesPolicy) XCTAssertEqual("two", allFilesPolicy?.identifier) XCTAssertEqual("twoReq", allFilesPolicy?.codeRequirement) XCTAssertEqual("bundleID", allFilesPolicy?.identifierType) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index bdddc11..37b28c4 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -360,5 +360,5 @@ struct UploadInfoView: View { } #Preview { - UploadInfoView(signingIdentities: [], dismissAction: {}) + UploadInfoView(signingIdentities: []) {} } diff --git a/Source/View Controllers/TCCProfileViewController.swift b/Source/View Controllers/TCCProfileViewController.swift index 5628215..e23b53f 100644 --- a/Source/View Controllers/TCCProfileViewController.swift +++ b/Source/View Controllers/TCCProfileViewController.swift @@ -188,7 +188,7 @@ class TCCProfileViewController: NSViewController { } let hostingController = NSHostingController( - rootView: UploadInfoView(signingIdentities: identities, dismissAction: {}) + rootView: UploadInfoView(signingIdentities: identities) {} ) hostingController.rootView = UploadInfoView(signingIdentities: identities) { [weak self, weak hostingController] in guard let self, let controller = hostingController else { return } From 75cd55ee0160e3c1ba49930805f0b59509472cf3 Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Tue, 17 Mar 2026 10:23:52 -0500 Subject: [PATCH 3/8] JPCFM-5534 story/JPCFM-5534 add file length swift lint back in --- Source/View Controllers/TCCProfileViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/View Controllers/TCCProfileViewController.swift b/Source/View Controllers/TCCProfileViewController.swift index e23b53f..5c69e40 100644 --- a/Source/View Controllers/TCCProfileViewController.swift +++ b/Source/View Controllers/TCCProfileViewController.swift @@ -24,6 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // +// swiftlint:disable file_length import Cocoa import OSLog From 1bb53fd0873b17bda8e732693ea626543ba88368 Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Tue, 17 Mar 2026 13:29:53 -0500 Subject: [PATCH 4/8] JPCFM-5534 story/JPCFM-5534 Add Task calls around async calls and adjust how isDismissed is handled --- Source/SwiftUI/UploadInfoView.swift | 71 ++++++++++++++++------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 37b28c4..94f7b87 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -149,6 +149,9 @@ struct UploadInfoView: View { payloadId = tccProfile.identifier } } + .onDisappear { + isDismissed = true + } } /// Creates a hash of the currently entered connection info @@ -282,29 +285,31 @@ struct UploadInfoView: View { let uploadMgr = UploadManager(serverURL: serverURL) uploadMgr.verifyConnection(authManager: makeAuthManager()) { result in - guard !isDismissed else { return } - - if case .success(let success) = result { - mustSign = success.mustSign - organization = success.organization - verifiedConnectionHash = hashOfConnectionInfo - if saveToKeychain { - do { - try SecurityWrapper.saveCredentials(username: username, - password: password, - server: serverURL) - } catch { - logger.error("Failed to save credentials with error: \(error.localizedDescription)") - } - } - // Future on macOS 12+: focus on Payload Name field - } else if case .failure(let failure) = result, - case .anyError(let errorString) = failure { - warningInfo = errorString - verifiedConnectionHash = 0 - } - - networkOperationInfo = nil + Task { @MainActor in + guard !isDismissed else { return } + + if case .success(let success) = result { + mustSign = success.mustSign + organization = success.organization + verifiedConnectionHash = hashOfConnectionInfo + if saveToKeychain { + do { + try SecurityWrapper.saveCredentials(username: username, + password: password, + server: serverURL) + } catch { + logger.error("Failed to save credentials with error: \(error.localizedDescription)") + } + } + // Future on macOS 12+: focus on Payload Name field + } else if case .failure(let failure) = result, + case .anyError(let errorString) = failure { + warningInfo = errorString + verifiedConnectionHash = 0 + } + + networkOperationInfo = nil + } } } @@ -346,15 +351,17 @@ struct UploadInfoView: View { authMgr: makeAuthManager(), siteInfo: siteIdAndName, signingIdentity: mustSign ? signingId : nil) { possibleError in - guard !isDismissed else { return } - - if let error = possibleError { - warningInfo = error.localizedDescription - } else { - Alert().display(header: "Success", message: "Profile uploaded succesfully") - dismissView() - } - networkOperationInfo = nil + Task { @MainActor in + guard !isDismissed else { return } + + if let error = possibleError { + warningInfo = error.localizedDescription + } else { + Alert().display(header: "Success", message: "Profile uploaded succesfully") + dismissView() + } + networkOperationInfo = nil + } } } } From 8fff2f0184790bebd56a8db90915871ad7be9135 Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Tue, 17 Mar 2026 15:39:52 -0500 Subject: [PATCH 5/8] JPCFM-5534 story/JPCFM-5534 Simplify upload manager section of code --- Source/SwiftUI/UploadInfoView.swift | 56 ++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 94f7b87..837713c 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -280,36 +280,42 @@ struct UploadInfoView: View { guard connectionInfoPassesValidation(setWarningInfo: true) else { return } - + let requestHash = hashOfConnectionInfo networkOperationInfo = "Checking Jamf Pro server" let uploadMgr = UploadManager(serverURL: serverURL) uploadMgr.verifyConnection(authManager: makeAuthManager()) { result in - Task { @MainActor in - guard !isDismissed else { return } - - if case .success(let success) = result { - mustSign = success.mustSign - organization = success.organization - verifiedConnectionHash = hashOfConnectionInfo - if saveToKeychain { - do { - try SecurityWrapper.saveCredentials(username: username, - password: password, - server: serverURL) - } catch { - logger.error("Failed to save credentials with error: \(error.localizedDescription)") - } - } - // Future on macOS 12+: focus on Payload Name field - } else if case .failure(let failure) = result, - case .anyError(let errorString) = failure { - warningInfo = errorString - verifiedConnectionHash = 0 - } + Task { @MainActor in + guard !isDismissed else { return } + + switch result { + case .success(let success): + mustSign = success.mustSign + organization = success.organization + verifiedConnectionHash = requestHash + if saveToKeychain { + let user = username + let pass = password + let server = serverURL + Task.detached { + do { + try SecurityWrapper.saveCredentials(username: user, + password: pass, + server: server) + } catch { + logger.error("Failed to save credentials with error: \(error.localizedDescription)") + } + } + } + case .failure(let failure): + if case .anyError(let errorString) = failure { + warningInfo = errorString + } + verifiedConnectionHash = 0 + } - networkOperationInfo = nil - } + networkOperationInfo = nil + } } } From c1353df156f48b89d209a306ea5b8878dccdae4a Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Wed, 18 Mar 2026 09:15:30 -0500 Subject: [PATCH 6/8] JPCFM-5534 story/JPCFM-5534 Remove redundant Main Actor call and fix spelling mistake --- Source/SwiftUI/UploadInfoView.swift | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 837713c..23f702f 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -357,17 +357,15 @@ struct UploadInfoView: View { authMgr: makeAuthManager(), siteInfo: siteIdAndName, signingIdentity: mustSign ? signingId : nil) { possibleError in - Task { @MainActor in - guard !isDismissed else { return } - - if let error = possibleError { - warningInfo = error.localizedDescription - } else { - Alert().display(header: "Success", message: "Profile uploaded succesfully") - dismissView() - } - networkOperationInfo = nil + guard !isDismissed else { return } + + if let error = possibleError { + warningInfo = error.localizedDescription + } else { + Alert().display(header: "Success", message: "Profile uploaded successfully") + dismissView() } + networkOperationInfo = nil } } } From 153bdeb95418eb4c50e004088a85720c988087d0 Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Wed, 18 Mar 2026 09:26:38 -0500 Subject: [PATCH 7/8] JPCFM-5534 story/JPCFM-5534 Take another keychain call off the main actor --- Source/SwiftUI/UploadInfoView.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 23f702f..cb8b854 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -320,13 +320,21 @@ struct UploadInfoView: View { } private func dismissView() { + // Capture values needed for potential background credential removal + let shouldRemoveCredentials = !saveToKeychain + let server = serverURL + let user = username + isDismissed = true - if !saveToKeychain { - try? SecurityWrapper.removeCredentials(server: serverURL, username: username) - } + // Dismiss the view immediately to keep the UI responsive + dismissAction() - dismissAction() + if shouldRemoveCredentials { + Task.detached { + try? SecurityWrapper.removeCredentials(server: server, username: user) + } + } } func performUpload() { From a974cf75cad91b73eb297d54ce9c305a55b881fa Mon Sep 17 00:00:00 2001 From: JJ Pritzl Date: Wed, 18 Mar 2026 09:41:24 -0500 Subject: [PATCH 8/8] JPCFM-5534 story/JPCFM-5534 Fix indentation and add a guard around hashOfConnectionInfo before doing things with it --- Source/SwiftUI/UploadInfoView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index cb8b854..4a49fd3 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -286,7 +286,7 @@ struct UploadInfoView: View { let uploadMgr = UploadManager(serverURL: serverURL) uploadMgr.verifyConnection(authManager: makeAuthManager()) { result in Task { @MainActor in - guard !isDismissed else { return } + guard !isDismissed, requestHash == hashOfConnectionInfo else { return } switch result { case .success(let success): @@ -325,16 +325,16 @@ struct UploadInfoView: View { let server = serverURL let user = username - isDismissed = true + isDismissed = true // Dismiss the view immediately to keep the UI responsive dismissAction() - if shouldRemoveCredentials { + if shouldRemoveCredentials { Task.detached { try? SecurityWrapper.removeCredentials(server: server, username: user) } - } + } } func performUpload() {