From fe1e2861823723a8710f40d8b526a9b11367c6e9 Mon Sep 17 00:00:00 2001 From: Alexander Heinrich Date: Fri, 21 Apr 2023 09:54:51 +0200 Subject: [PATCH] Compatibility with macOS 13.3 --- .../xcshareddata/swiftpm/Package.resolved | 60 ++++--- .../OpenHaystack/AnisetteDataManager.swift | 3 +- .../HaystackApp/AccessoryController.swift | 40 ++--- .../HaystackApp/Model/PreviewData.swift | 1 - .../HaystackApp/UpdateCheckController.swift | 162 +++++++++--------- .../Views/AccessoryListEntry.swift | 24 ++- .../HaystackApp/Views/ActivityIndicator.swift | 2 +- .../HaystackApp/Views/NRFInstallSheet.swift | 8 +- .../OpenHaystack/OpenHaystackApp.swift | 6 +- OpenHaystack/OpenHaystackMail/Info.plist | 43 +++++ .../OpenHaystackTests/UpdateCheckTests.swift | 43 ++--- 11 files changed, 226 insertions(+), 166 deletions(-) diff --git a/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d24976a..7b30c94 100644 --- a/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OpenHaystack/OpenHaystack.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,34 +1,32 @@ { - "object": { - "pins": [ - { - "package": "swift-crypto", - "repositoryURL": "https://github.com/apple/swift-crypto.git", - "state": { - "branch": null, - "revision": "3bea268b223651c4ab7b7b9ad62ef9b2d4143eb6", - "version": "1.1.6" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "6aa9347d9bc5bbfe6a84983aec955c17ffea96ef", - "version": "2.33.0" - } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl", - "state": { - "branch": null, - "revision": "5e68c1ded15619bb281b273fa8c2d8fd7f7b2b7d", - "version": "2.16.1" - } + "pins" : [ + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "ddb07e896a2a8af79512543b1c7eb9797f8898a5", + "version" : "1.1.7" } - ] - }, - "version": 1 + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "124119f0bb12384cef35aa041d7c3a686108722d", + "version" : "2.40.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl", + "state" : { + "revision" : "c30c680c78c99afdabf84805a83c8745387c4ac7", + "version" : "2.20.2" + } + } + ], + "version" : 2 } diff --git a/OpenHaystack/OpenHaystack/AnisetteDataManager.swift b/OpenHaystack/OpenHaystack/AnisetteDataManager.swift index e6d499d..5a20ea0 100644 --- a/OpenHaystack/OpenHaystack/AnisetteDataManager.swift +++ b/OpenHaystack/OpenHaystack/AnisetteDataManager.swift @@ -98,7 +98,8 @@ public class AnisetteDataManager: NSObject { "X-Apple-I-MD-M": data.machineID, "X-Apple-I-MD": data.oneTimePassword, "X-Apple-I-TimeZone": String(data.timeZone.abbreviation() ?? "UTC"), - "X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date), + // "X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date), + "X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: Date()), "X-Apple-I-MD-RINFO": String(data.routingInfo), ] as [AnyHashable: Any]) } diff --git a/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift b/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift index 1689ffe..3c2adc1 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift @@ -17,7 +17,7 @@ class AccessoryController: ObservableObject { var selfObserver: AnyCancellable? var listElementsObserver = [AnyCancellable]() let findMyController: FindMyController - + weak var savePanel: NSSavePanel? init(accessories: [Accessory], findMyController: FindMyController) { @@ -99,13 +99,13 @@ class AccessoryController: ObservableObject { func export(accessories: [Accessory]) throws -> URL { let savePanel = NSSavePanel() -// savePanel.allowedFileTypes = ["plist", "json"] + // savePanel.allowedFileTypes = ["plist", "json"] if #available(macOS 12.0, *) { savePanel.allowedContentTypes = [.propertyList] - }else { + } else { savePanel.allowedFileTypes = ["plist"] } - + savePanel.canCreateDirectories = true savePanel.directoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) savePanel.message = "This export contains all private keys! Keep the file save to protect your location data" @@ -114,7 +114,7 @@ class AccessoryController: ObservableObject { savePanel.prompt = "Export" savePanel.title = "Export accessories & keys" savePanel.isExtensionHidden = false - + let accessoryView = NSView() let popUpButton = NSPopUpButton(title: "File type", target: self, action: #selector(exportFileTypeChanged(button:))) popUpButton.addItems(withTitles: ["Property List", "JSON"]) @@ -122,23 +122,23 @@ class AccessoryController: ObservableObject { popUpButton.stringValue = "File type" popUpButton.translatesAutoresizingMaskIntoConstraints = false accessoryView.addSubview(popUpButton) - + let popUpButtonLabel = NSTextField(labelWithString: "File type") popUpButtonLabel.translatesAutoresizingMaskIntoConstraints = false accessoryView.addSubview(popUpButtonLabel) accessoryView.translatesAutoresizingMaskIntoConstraints = false - -// popUpButtonLabel.leadingAnchor.constraint(greaterThanOrEqualTo: accessoryView.leadingAnchor, constant: 20.0).isActive = true + + // popUpButtonLabel.leadingAnchor.constraint(greaterThanOrEqualTo: accessoryView.leadingAnchor, constant: 20.0).isActive = true popUpButtonLabel.trailingAnchor.constraint(equalTo: popUpButton.leadingAnchor, constant: -8.0).isActive = true popUpButtonLabel.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.centerXAnchor, constant: 0).isActive = true popUpButtonLabel.centerYAnchor.constraint(equalTo: popUpButton.centerYAnchor, constant: 0).isActive = true -// popUpButton.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.trailingAnchor, constant: -20.0).isActive = true + // popUpButton.trailingAnchor.constraint(lessThanOrEqualTo: accessoryView.trailingAnchor, constant: -20.0).isActive = true popUpButton.leadingAnchor.constraint(lessThanOrEqualTo: accessoryView.centerXAnchor, constant: 0).isActive = true popUpButton.topAnchor.constraint(equalTo: accessoryView.topAnchor, constant: 8.0).isActive = true popUpButton.bottomAnchor.constraint(equalTo: accessoryView.bottomAnchor, constant: -8.0).isActive = true popUpButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 20.0).isActive = true popUpButton.widthAnchor.constraint(lessThanOrEqualToConstant: 200.0).isActive = true - + savePanel.accessoryView = accessoryView self.savePanel = savePanel @@ -148,7 +148,7 @@ class AccessoryController: ObservableObject { var url = savePanel.url { let selectedItemIndex = popUpButton.indexOfSelectedItem - + // Store the accessory file if selectedItemIndex == 0 { if url.pathExtension != "plist" { @@ -156,7 +156,7 @@ class AccessoryController: ObservableObject { } let propertyList = try PropertyListEncoder().encode(accessories) try propertyList.write(to: url) - }else if selectedItemIndex == 1 { + } else if selectedItemIndex == 1 { if url.pathExtension != "json" { url = url.appendingPathExtension("json") } @@ -168,18 +168,18 @@ class AccessoryController: ObservableObject { } throw ImportError.cancelled } - + @objc func exportFileTypeChanged(button: NSPopUpButton) { if button.indexOfSelectedItem == 0 { if #available(macOS 12.0, *) { self.savePanel?.allowedContentTypes = [.propertyList] - }else { + } else { self.savePanel?.allowedFileTypes = ["plist"] } - }else { + } else { if #available(macOS 12.0, *) { self.savePanel?.allowedContentTypes = [.json] - }else { + } else { self.savePanel?.allowedFileTypes = ["json"] } } @@ -190,10 +190,10 @@ class AccessoryController: ObservableObject { let openPanel = NSOpenPanel() if #available(macOS 12.0, *) { openPanel.allowedContentTypes = [.json, .propertyList] - }else { + } else { openPanel.allowedFileTypes = ["json", "plist"] } - + openPanel.canCreateDirectories = true openPanel.directoryURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) openPanel.message = "Import an accessories file that includes the private keys" @@ -208,10 +208,10 @@ class AccessoryController: ObservableObject { var importedAccessories: [Accessory] if url.pathExtension == "plist" { importedAccessories = try PropertyListDecoder().decode([Accessory].self, from: accessoryData) - }else { + } else { importedAccessories = try JSONDecoder().decode([Accessory].self, from: accessoryData) } - + var updatedAccessories = self.accessories // Filter out accessories with the same id (no duplicates) importedAccessories = importedAccessories.filter({ acc in !self.accessories.contains(where: { acc.id == $0.id }) }) diff --git a/OpenHaystack/OpenHaystack/HaystackApp/Model/PreviewData.swift b/OpenHaystack/OpenHaystack/HaystackApp/Model/PreviewData.swift index 414e209..8e1cbba 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/Model/PreviewData.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/Model/PreviewData.swift @@ -10,7 +10,6 @@ import CoreLocation import Foundation import SwiftUI -import CoreLocation // swiftlint:disable force_try struct PreviewData { diff --git a/OpenHaystack/OpenHaystack/HaystackApp/UpdateCheckController.swift b/OpenHaystack/OpenHaystack/HaystackApp/UpdateCheckController.swift index 66af6cd..0368c0c 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/UpdateCheckController.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/UpdateCheckController.swift @@ -7,83 +7,83 @@ // SPDX-License-Identifier: AGPL-3.0-only // -import Foundation import AppKit - +import Foundation /// Can check if a new OpenHaystack version is needed and download it. public struct UpdateCheckController { - + public static func checkForNewVersion() { // Load the GitHub Releases page let releasesURL = URL(string: "https://github.com/seemoo-lab/openhaystack/releases")! URLSession.shared.dataTask(with: releasesURL) { optionalData, response, error in guard let data = optionalData, - (response as? HTTPURLResponse)?.statusCode == 200, - let htmlString = String(data:data, encoding: .utf8) + (response as? HTTPURLResponse)?.statusCode == 200, + let htmlString = String(data: data, encoding: .utf8) else { return } - - + guard let availableVersion = getVersion(from: htmlString) else { return } - + //Get installed version let version = Bundle.main.infoDictionary?["CFBundleVersionShortString"] as? String ?? "0" - + let comparisonResult = compareVersions(availableVersion: availableVersion, installedVersion: version) - + DispatchQueue.main.async { if comparisonResult == .older, askToDownloadUpdate() == .alertSecondButtonReturn { //The currently installed version is older. Install an update - self.downloadUpdate(version: availableVersion, finished: { success in - if success { - let result = successDownloadAlert() - if result == .alertSecondButtonReturn { - //Open the download folder - let downloadURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] - NSWorkspace.shared.open(downloadURL) - } - }else { - if downloadFailedAlert() == .alertSecondButtonReturn { - NSWorkspace.shared.open(URL(string: "https://github.com/seemoo-lab/openhaystack/releases")!) + self.downloadUpdate( + version: availableVersion, + finished: { success in + if success { + let result = successDownloadAlert() + if result == .alertSecondButtonReturn { + //Open the download folder + let downloadURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] + NSWorkspace.shared.open(downloadURL) + } + } else { + if downloadFailedAlert() == .alertSecondButtonReturn { + NSWorkspace.shared.open(URL(string: "https://github.com/seemoo-lab/openhaystack/releases")!) + } } - } - }) + }) } } - + }.resume() } - + internal static func getVersion(from htmlString: String) -> String? { guard let regex = try? NSRegularExpression(pattern: "Release (v[0-9]+(.[0-9]+)?(.[0-9]+)?)") else { return nil } - + let htmlNSString = htmlString as NSString - + let htmlRange = NSRange(location: 0, length: htmlNSString.length) - + if let checkResult = regex.firstMatch(in: htmlNSString as String, options: [], range: htmlRange), - checkResult.numberOfRanges >= 2 { - + checkResult.numberOfRanges >= 2 + { + //Get the latest release version range // A result should have multiple ranges for each capture group. 1 is the capture group for the version number let releaseVersionRange = checkResult.range(at: 1) let releaseVersion = htmlNSString.substring(with: releaseVersionRange) - + let releaseVersionNumber = releaseVersion.replacingOccurrences(of: "v", with: "") - + return releaseVersionNumber } - + return nil } - - + /// Compares two version strings and returns if the installed version is older, newer or the same /// - Parameters: /// - availableVersion: The latest available version @@ -92,110 +92,110 @@ public struct UpdateCheckController { internal static func compareVersions(availableVersion: String, installedVersion: String) -> VersionCompare { let availableVersionSplit = availableVersion.split(separator: ".") let installedVersionSplit = installedVersion.split(separator: ".") - + for (idx, availableVersionPart) in availableVersionSplit.enumerated() { - + if idx < installedVersionSplit.count { guard let avpi = Int(availableVersionPart), - let ivpi = Int(installedVersionSplit[idx]) else {return .older} - + let ivpi = Int(installedVersionSplit[idx]) + else { return .older } + if avpi > ivpi { return .older - }else if ivpi > avpi { + } else if ivpi > avpi { return .newer } - - }else { + + } else { //The installed version is x.x // The new version is x.x.y so it must be older return .older } } - + if installedVersionSplit.count > availableVersionSplit.count { //The installed version has a higher sub-version. So it must be newer return .newer } - + // All numbers were equal return .same } - + enum VersionCompare { case same, newer, older } - - - static func downloadUpdate(version: String, finished: @escaping (Bool)->()) { - + + static func downloadUpdate(version: String, finished: @escaping (Bool) -> Void) { + //Download the current version into a file in Downloads let downloadURL = URL(string: "https://github.com/seemoo-lab/openhaystack/releases/download/v\(version)/OpenHaystack.zip")! - + let task = URLSession.shared.downloadTask(with: downloadURL) { optionalFileURL, response, error in - - guard let downloadLocation = optionalFileURL else { - finished(false) - return - } - - //Move the file to the downloads folder - let downloadURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] - let openHaystackURL = downloadURL.appendingPathComponent("OpenHaystack.zip") - do { - let fm = FileManager.default - if fm.fileExists(atPath: openHaystackURL.path) { - _ = try fm.replaceItemAt(openHaystackURL, withItemAt: downloadLocation) - }else { - try fm.moveItem(at: downloadLocation, to: openHaystackURL) - } - - DispatchQueue.main.async {finished(true)} - }catch let error { - print(error.localizedDescription) - DispatchQueue.main.async {finished(false)} - + + guard let downloadLocation = optionalFileURL else { + finished(false) + return + } + + //Move the file to the downloads folder + let downloadURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] + let openHaystackURL = downloadURL.appendingPathComponent("OpenHaystack.zip") + do { + let fm = FileManager.default + if fm.fileExists(atPath: openHaystackURL.path) { + _ = try fm.replaceItemAt(openHaystackURL, withItemAt: downloadLocation) + } else { + try fm.moveItem(at: downloadLocation, to: openHaystackURL) } + + DispatchQueue.main.async { finished(true) } + } catch let error { + print(error.localizedDescription) + DispatchQueue.main.async { finished(false) } + + } } - + task.resume() } - + private static func askToDownloadUpdate() -> NSApplication.ModalResponse { let alert = NSAlert() alert.messageText = NSLocalizedString("New version available", comment: "Alert title") alert.informativeText = NSLocalizedString("A new version of OpenHaystack is available. Do you want to download it now?", comment: "Alert text") alert.addButton(withTitle: "Cancel") alert.addButton(withTitle: "Download") - + return alert.runModal() } - + private static func successDownloadAlert() -> NSApplication.ModalResponse { let alert = NSAlert() alert.messageText = NSLocalizedString("Successfully downloaded update", comment: "Alert title") alert.informativeText = NSLocalizedString("The new version has been downloaded successfully and it was placed in your Downloads folder.", comment: "Alert text") alert.addButton(withTitle: "Okay") alert.addButton(withTitle: "Open folder") - + return alert.runModal() } - + private static func downloadFailedAlert() -> NSApplication.ModalResponse { let alert = NSAlert() alert.messageText = NSLocalizedString("Download failed", comment: "Alert title") alert.informativeText = NSLocalizedString("To update to the newest version, please open the releases page on GitHub", comment: "Alert text") alert.addButton(withTitle: "Cancel") alert.addButton(withTitle: "Open") - + return alert.runModal() } - + } extension String { func substring(from range: NSRange) -> String { let substring = self[self.index(startIndex, offsetBy: range.lowerBound)..224E7F96-2099-499C-A501-63FB68C79CD2 A4B49485-0377-4FAB-8D8E-E3B8018CFC21 + Supported13.0PluginCompatibilityUUIDs + + 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 + 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 + 224E7F96-2099-499C-A501-63FB68C79CD2 + A4B49485-0377-4FAB-8D8E-E3B8018CFC21 + + Supported13.1PluginCompatibilityUUIDs + + 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 + 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 + 224E7F96-2099-499C-A501-63FB68C79CD2 + 890E3F5B-9490-4828-8F3F-B6561E513FCC + A4B49485-0377-4FAB-8D8E-E3B8018CFC21 + 281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE + + Supported13.2PluginCompatibilityUUIDs + + 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 + 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 + 890E3F5B-9490-4828-8F3F-B6561E513FCC + 224E7F96-2099-499C-A501-63FB68C79CD2 + 281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE + A4B49485-0377-4FAB-8D8E-E3B8018CFC21 + + Supported13.3PluginCompatibilityUUIDs + + 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 + 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 + 890E3F5B-9490-4828-8F3F-B6561E513FCC + 224E7F96-2099-499C-A501-63FB68C79CD2 + A4B49485-0377-4FAB-8D8E-E3B8018CFC21 + 281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE + + Supported13.4PluginCompatibilityUUIDs + + 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 + 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 + 224E7F96-2099-499C-A501-63FB68C79CD2 + 890E3F5B-9490-4828-8F3F-B6561E513FCC + A4B49485-0377-4FAB-8D8E-E3B8018CFC21 + 281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE + diff --git a/OpenHaystack/OpenHaystackTests/UpdateCheckTests.swift b/OpenHaystack/OpenHaystackTests/UpdateCheckTests.swift index daadbd9..d24bc32 100644 --- a/OpenHaystack/OpenHaystackTests/UpdateCheckTests.swift +++ b/OpenHaystack/OpenHaystackTests/UpdateCheckTests.swift @@ -9,46 +9,47 @@ import Foundation import XCTest + @testable import OpenHaystack class UpdateCheckTests: XCTestCase { - + func testCompareVersions() { let i1 = "1.0.3" let a1 = "1.0.4" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a1, installedVersion: i1), .older) let a11 = "1.1" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a11, installedVersion: i1), .older) - let a12 = "2" + let a12 = "2" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a12, installedVersion: i1), .older) - + let a2 = "1.0.3" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a2, installedVersion: i1), .same) - + let a3 = "1.0.2" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a3, installedVersion: i1), .newer) let a31 = "1.0" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a31, installedVersion: i1), .newer) let a32 = "0.10.1" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a32, installedVersion: i1), .newer) - + let a4 = "1.1.1" let i4 = "1.1.2" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a4, installedVersion: i4), .newer) let a41 = "1.0.2" XCTAssertEqual(UpdateCheckController.compareVersions(availableVersion: a41, installedVersion: i1), .newer) } - + func testHTMLVersionCompare() { let github = - """ -

Release v0.4.1

-

Release v0.4.1

- Release v0.4.1 - """ - + """ +

Release v0.4.1

+

Release v0.4.1

+ Release v0.4.1 + """ + XCTAssertEqual(UpdateCheckController.getVersion(from: github), "0.4.1") - + let h1 = "

Release v0.4.1

Release v0.3.1

" XCTAssertEqual(UpdateCheckController.getVersion(from: h1), "0.4.1") let h2 = "

Release v0.5

" @@ -58,16 +59,16 @@ class UpdateCheckTests: XCTestCase { let h4 = "

Release v1

" XCTAssertEqual(UpdateCheckController.getVersion(from: h4), "1") } - + func testDownload() { let expect = expectation(description: "Update download") - UpdateCheckController.downloadUpdate(version: "0.4.1", finished: { success in - XCTAssertTrue(success) - expect.fulfill() - }) + UpdateCheckController.downloadUpdate( + version: "0.4.1", + finished: { success in + XCTAssertTrue(success) + expect.fulfill() + }) wait(for: [expect], timeout: 20.0) - + } } - -