diff --git a/Sources/Packages/Sources/Brief/Documentation.docc/Documentation.md b/Sources/Packages/Sources/Brief/Documentation.docc/Documentation.md new file mode 100644 index 0000000..fe1562c --- /dev/null +++ b/Sources/Packages/Sources/Brief/Documentation.docc/Documentation.md @@ -0,0 +1,15 @@ +# ``Brief`` + +Brief is a collection of protocols and concrete implmentation describing updates. + +## Topics + +### Versioning + +- ``SemVer`` +- ``Release`` + +### Updater + +- ``UpdaterProtocol`` +- ``Updater`` diff --git a/Sources/Packages/Sources/Brief/Release.swift b/Sources/Packages/Sources/Brief/Release.swift new file mode 100644 index 0000000..847dffe --- /dev/null +++ b/Sources/Packages/Sources/Brief/Release.swift @@ -0,0 +1,80 @@ +import Foundation + +/// A release is a representation of a downloadable update. +public struct Release: Codable { + + /// The user-facing name of the release. Typically "Secretive 1.2.3" + public let name: String + + /// A boolean describing whether or not the release is a prerelase build. + public let prerelease: Bool + + /// A URL pointing to the HTML page for the release. + public let html_url: URL + + /// A user-facing description of the contents of the update. + public let body: String + + /// Initializes a Release. + /// - Parameters: + /// - name: The user-facing name of the release. + /// - prerelease: A boolean describing whether or not the release is a prerelase build. + /// - html_url: A URL pointing to the HTML page for the release. + /// - body: A user-facing description of the contents of the update. + public init(name: String, prerelease: Bool, html_url: URL, body: String) { + self.name = name + self.prerelease = prerelease + self.html_url = html_url + self.body = body + } + +} + +extension Release: Identifiable { + + public var id: String { + html_url.absoluteString + } + +} + +extension Release: Comparable { + + public static func < (lhs: Release, rhs: Release) -> Bool { + lhs.version < rhs.version + } + +} + +extension Release { + + /// A boolean describing whether or not the release contains critical security content. + /// - Note: this is determined by the presence of the phrase "Critical Security Update" in the ``body``. + /// - Warning: If this property is true, the user will not be able to dismiss UI or reminders associated with the update. + public var critical: Bool { + body.contains(Constants.securityContent) + } + + /// A ``SemVer`` representation of the version number of the release. + public var version: SemVer { + SemVer(name) + } + + /// The minimum macOS version required to run the update. + public var minimumOSVersion: SemVer { + guard let range = body.range(of: "Minimum macOS Version"), + let numberStart = body.rangeOfCharacter(from: CharacterSet.decimalDigits, options: [], range: range.upperBound.. Bool { + for (latest, current) in zip(lhs.versionNumbers, rhs.versionNumbers) { + if latest < current { + return true + } else if latest > current { + return false + } + } + return false + } + + +} diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index ded6b7a..c71e538 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -1,22 +1,24 @@ import Foundation import Combine -public protocol UpdaterProtocol: ObservableObject { - - var update: Release? { get } - var testBuild: Bool { get } -} - +/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version. public class Updater: ObservableObject, UpdaterProtocol { @Published public var update: Release? - public let testBuild: Bool + /// The current OS version. private let osVersion: SemVer + /// The current version of the app that is running. private let currentVersion: SemVer - public init(checkOnLaunch: Bool, osVersion: SemVer = SemVer(ProcessInfo.processInfo.operatingSystemVersion), currentVersion: SemVer = SemVer(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0")) { + /// Initializes an Updater. + /// - Parameters: + /// - checkOnLaunch: A boolean describing whether the Updater should check for available updates on launch. + /// - checkFrequency: The interval at which the Updater should check for updates. Subject to a tolerance of 1 hour. + /// - osVersion: The current OS version. + /// - currentVersion: The current version of the app that is running. + public init(checkOnLaunch: Bool, checkFrequency: TimeInterval = Measurement(value: 24, unit: UnitDuration.hours).converted(to: .seconds).value, osVersion: SemVer = SemVer(ProcessInfo.processInfo.operatingSystemVersion), currentVersion: SemVer = SemVer(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0")) { self.osVersion = osVersion self.currentVersion = currentVersion testBuild = currentVersion == SemVer("0.0.0") @@ -24,12 +26,13 @@ public class Updater: ObservableObject, UpdaterProtocol { // Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet. checkForUpdates() } - let timer = Timer.scheduledTimer(withTimeInterval: 60*60*24, repeats: true) { _ in + let timer = Timer.scheduledTimer(withTimeInterval: checkFrequency, repeats: true) { _ in self.checkForUpdates() } timer.tolerance = 60*60 } + /// Manually trigger an update check. public func checkForUpdates() { URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in guard let data = data else { return } @@ -38,6 +41,8 @@ public class Updater: ObservableObject, UpdaterProtocol { }.resume() } + /// Ignores a specified release. `update` will be nil if the user has ignored the latest available release. + /// - Parameter release: The release to ignore. public func ignore(release: Release) { guard !release.critical else { return } defaults.set(true, forKey: release.name) @@ -50,6 +55,8 @@ public class Updater: ObservableObject, UpdaterProtocol { extension Updater { + /// Evaluates the available downloadable releases, and selects the newest non-prerelease release that the user is able to run. + /// - Parameter releases: An array of ``Release`` objects. func evaluate(releases: [Release]) { guard let release = releases .sorted() @@ -66,49 +73,18 @@ extension Updater { } } + /// Checks whether the user has ignored a release. + /// - Parameter release: The release to check. + /// - Returns: A boolean describing whether the user has ignored the release. Will always be false if the release is critical. func userIgnored(release: Release) -> Bool { guard !release.critical else { return false } return defaults.bool(forKey: release.name) } + /// The user defaults used to store user ignore state. var defaults: UserDefaults { UserDefaults(suiteName: "com.maxgoedjen.Secretive.updater.ignorelist")! } -} - -public struct SemVer { - - let versionNumbers: [Int] - - public init(_ version: String) { - // Betas have the format 1.2.3_beta1 - let strippedBeta = version.split(separator: "_").first! - var split = strippedBeta.split(separator: ".").compactMap { Int($0) } - while split.count < 3 { - split.append(0) - } - versionNumbers = split - } - - public init(_ version: OperatingSystemVersion) { - versionNumbers = [version.majorVersion, version.minorVersion, version.patchVersion] - } - -} - -extension SemVer: Comparable { - - public static func < (lhs: SemVer, rhs: SemVer) -> Bool { - for (latest, current) in zip(lhs.versionNumbers, rhs.versionNumbers) { - if latest < current { - return true - } else if latest > current { - return false - } - } - return false - } - } @@ -119,63 +95,3 @@ extension Updater { } } - -public struct Release: Codable { - - public let name: String - public let prerelease: Bool - public let html_url: URL - public let body: String - - public init(name: String, prerelease: Bool, html_url: URL, body: String) { - self.name = name - self.prerelease = prerelease - self.html_url = html_url - self.body = body - } - -} - -extension Release: Identifiable { - - public var id: String { - html_url.absoluteString - } - -} - -extension Release: Comparable { - - public static func < (lhs: Release, rhs: Release) -> Bool { - lhs.version < rhs.version - } - -} - -extension Release { - - public var critical: Bool { - body.contains(Constants.securityContent) - } - - public var version: SemVer { - SemVer(name) - } - - public var minimumOSVersion: SemVer { - guard let range = body.range(of: "Minimum macOS Version"), - let numberStart = body.rangeOfCharacter(from: CharacterSet.decimalDigits, options: [], range: range.upperBound..Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` diff --git a/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md b/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md index 6888611..8555c39 100644 --- a/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md +++ b/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md @@ -1,4 +1,4 @@ -# ````SecretKit```` +# ``SecretKit`` SecretKit is a collection of protocols describing secrets and stores. diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/Documentation.docc/Documentation.md b/Sources/Packages/Sources/SecureEnclaveSecretKit/Documentation.docc/Documentation.md index 1a1f46b..d153175 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/Documentation.docc/Documentation.md +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/Documentation.docc/Documentation.md @@ -1,3 +1,3 @@ -# ````SecureEnclaveSecretKit```` +# ``SecureEnclaveSecretKit`` SecureEnclaveSecretKit contains implementations of SecretKit protocols backed by the Secure Enclave. diff --git a/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/Documentation.md b/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/Documentation.md index b17f751..0826de9 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/Documentation.md +++ b/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/Documentation.md @@ -1,3 +1,3 @@ -# ````SmartCardSecretKit```` +# ``SmartCardSecretKit`` SmartCardSecretKit contains implementations of SecretKit protocols backed by a Smart Card.