mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-02-14 17:49:54 +00:00
Compatibility with macOS 13.3
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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 }) })
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
// swiftlint:disable force_try
|
||||
struct PreviewData {
|
||||
|
||||
@@ -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)
|
||||
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")!)
|
||||
}
|
||||
}
|
||||
}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)..<self.index(startIndex, offsetBy: range.upperBound)]
|
||||
|
||||
|
||||
return String(substring)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +98,13 @@ struct AccessoryListEntry: View {
|
||||
}
|
||||
Divider()
|
||||
Button("Mark as \(accessory.isDeployed ? "deployable" : "deployed")", action: { accessory.isDeployed.toggle() })
|
||||
|
||||
Button("Copy private Key B64", action: { copyPrivateKey(accessory: accessory) })
|
||||
|
||||
Group {
|
||||
Button("Copy private Key B64", action: { copyPrivateKey(accessory: accessory) })
|
||||
|
||||
Button("Export Locations", action: { exportLocations(accessory: accessory) })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,16 +195,27 @@ struct AccessoryListEntry: View {
|
||||
assert(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func copyPrivateKey(accessory: Accessory) {
|
||||
let privateKey = accessory.privateKey
|
||||
let keyB64 = privateKey.base64EncodedString()
|
||||
|
||||
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.prepareForNewContents(with: .currentHostOnly)
|
||||
pasteboard.setString(keyB64, forType: .string)
|
||||
}
|
||||
|
||||
func exportLocations(accessory: Accessory) {
|
||||
guard let locations = accessory.locations,
|
||||
let locationData = try? JSONEncoder().encode(locations)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let savePanel = SavePanel.shared
|
||||
savePanel.saveFile(file: locationData, fileExtension: "json")
|
||||
}
|
||||
|
||||
struct AccessoryListEntry_Previews: PreviewProvider {
|
||||
@StateObject static var accessory = PreviewData.accessories.first!
|
||||
@State static var alertType: OpenHaystackMainView.AlertType?
|
||||
|
||||
@@ -11,7 +11,7 @@ import AppKit
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class ActivityIndicator: NSViewRepresentable {
|
||||
struct ActivityIndicator: NSViewRepresentable {
|
||||
|
||||
init(size: NSControl.ControlSize) {
|
||||
self.size = size
|
||||
|
||||
@@ -43,9 +43,11 @@ struct NRFInstallSheet: View {
|
||||
|
||||
Divider()
|
||||
|
||||
Text("The new NRF firmware uses rotating keys. This means that the device changes its public key after a specific number of days. This disallows ad networks to track your device over several days when you are moving around the city. Shorter update cycles then days are not supported")
|
||||
Text(
|
||||
"The new NRF firmware uses rotating keys. This means that the device changes its public key after a specific number of days. This disallows ad networks to track your device over several days when you are moving around the city. Shorter update cycles then days are not supported"
|
||||
)
|
||||
self.timePicker
|
||||
|
||||
|
||||
Text("One day is a reasonable amount of time")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -82,7 +84,7 @@ struct NRFInstallSheet: View {
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Flashing from M1 Macs might fail due to missing ARM support by NRF")
|
||||
|
||||
@@ -15,7 +15,7 @@ struct OpenHaystackApp: App {
|
||||
var accessoryNearbyMonitor: AccessoryNearbyMonitor?
|
||||
var frameWidth: CGFloat? = nil
|
||||
var frameHeight: CGFloat? = nil
|
||||
|
||||
|
||||
@State var checkedForUpdates = false
|
||||
|
||||
init() {
|
||||
@@ -45,9 +45,9 @@ struct OpenHaystackApp: App {
|
||||
SidebarCommands()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func checkForUpdates() {
|
||||
guard checkedForUpdates == false, ProcessInfo().arguments.contains("-stopUpdateCheck") == false else {return}
|
||||
guard checkedForUpdates == false, ProcessInfo().arguments.contains("-stopUpdateCheck") == false else { return }
|
||||
UpdateCheckController.checkForNewVersion()
|
||||
checkedForUpdates = true
|
||||
}
|
||||
|
||||
@@ -178,5 +178,48 @@
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported13.0PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported13.1PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.2PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
</array>
|
||||
<key>Supported13.3PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
<key>Supported13.4PluginCompatibilityUUIDs</key>
|
||||
<array>
|
||||
<string>25288CEF-7D9B-49A8-BE6B-E41DA6277CF3</string>
|
||||
<string>6FF8B077-81FA-45A4-BD57-17CDE79F13A5</string>
|
||||
<string>224E7F96-2099-499C-A501-63FB68C79CD2</string>
|
||||
<string>890E3F5B-9490-4828-8F3F-B6561E513FCC</string>
|
||||
<string>A4B49485-0377-4FAB-8D8E-E3B8018CFC21</string>
|
||||
<string>281F8A5C-0AF9-4BE6-8B8A-C0CB9C2068BE</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -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 =
|
||||
"""
|
||||
<h1 data-view-component="true" class="d-inline mr-3"><a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a></h1>
|
||||
<h1 data-view-component="true" class="d-inline mr-3"><a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a></h1>
|
||||
<a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a>
|
||||
"""
|
||||
|
||||
"""
|
||||
<h1 data-view-component="true" class="d-inline mr-3"><a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a></h1>
|
||||
<h1 data-view-component="true" class="d-inline mr-3"><a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a></h1>
|
||||
<a href="/seemoo-lab/openhaystack/releases/tag/v0.4.1" data-view-component="true" class="Link--primary">Release v0.4.1</a>
|
||||
"""
|
||||
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: github), "0.4.1")
|
||||
|
||||
|
||||
let h1 = "<h1>Release v0.4.1</h1> <h1>Release v0.3.1</h1>"
|
||||
XCTAssertEqual(UpdateCheckController.getVersion(from: h1), "0.4.1")
|
||||
let h2 = "<h1>Release v0.5</h1>"
|
||||
@@ -58,16 +59,16 @@ class UpdateCheckTests: XCTestCase {
|
||||
let h4 = "<h1>Release v1</h1>"
|
||||
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)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user