mirror of
https://github.com/maxgoedjen/secretive.git
synced 2024-12-04 11:27:08 +00:00
Break up Brief + Document (#311)
This commit is contained in:
parent
eb282b4116
commit
da2c460c60
@ -0,0 +1,15 @@
|
|||||||
|
# ``Brief``
|
||||||
|
|
||||||
|
Brief is a collection of protocols and concrete implmentation describing updates.
|
||||||
|
|
||||||
|
## Topics
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
- ``SemVer``
|
||||||
|
- ``Release``
|
||||||
|
|
||||||
|
### Updater
|
||||||
|
|
||||||
|
- ``UpdaterProtocol``
|
||||||
|
- ``Updater``
|
80
Sources/Packages/Sources/Brief/Release.swift
Normal file
80
Sources/Packages/Sources/Brief/Release.swift
Normal file
@ -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..<body.endIndex) else { return SemVer("11.0.0") }
|
||||||
|
let numbersEnd = body.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines, options: [], range: numberStart.upperBound..<body.endIndex)?.lowerBound ?? body.endIndex
|
||||||
|
let version = numberStart.lowerBound..<numbersEnd
|
||||||
|
return SemVer(String(body[version]))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Release {
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
static let securityContent = "Critical Security Update"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
Sources/Packages/Sources/Brief/SemVer.swift
Normal file
43
Sources/Packages/Sources/Brief/SemVer.swift
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// A representation of a Semantic Version.
|
||||||
|
public struct SemVer {
|
||||||
|
|
||||||
|
/// The SemVer broken into an array of integers.
|
||||||
|
let versionNumbers: [Int]
|
||||||
|
|
||||||
|
/// Initializes a SemVer from a string representation.
|
||||||
|
/// - Parameter version: A string representation of the SemVer, formatted as "major.minor.patch".
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes a SemVer from an ``OperatingSystemVersion`` representation.
|
||||||
|
/// - Parameter version: An ``OperatingSystemVersion`` representation of the SemVer.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,22 +1,24 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
public protocol UpdaterProtocol: ObservableObject {
|
/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version.
|
||||||
|
|
||||||
var update: Release? { get }
|
|
||||||
var testBuild: Bool { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Updater: ObservableObject, UpdaterProtocol {
|
public class Updater: ObservableObject, UpdaterProtocol {
|
||||||
|
|
||||||
@Published public var update: Release?
|
@Published public var update: Release?
|
||||||
|
|
||||||
public let testBuild: Bool
|
public let testBuild: Bool
|
||||||
|
|
||||||
|
/// The current OS version.
|
||||||
private let osVersion: SemVer
|
private let osVersion: SemVer
|
||||||
|
/// The current version of the app that is running.
|
||||||
private let currentVersion: SemVer
|
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.osVersion = osVersion
|
||||||
self.currentVersion = currentVersion
|
self.currentVersion = currentVersion
|
||||||
testBuild = currentVersion == SemVer("0.0.0")
|
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.
|
// Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet.
|
||||||
checkForUpdates()
|
checkForUpdates()
|
||||||
}
|
}
|
||||||
let timer = Timer.scheduledTimer(withTimeInterval: 60*60*24, repeats: true) { _ in
|
let timer = Timer.scheduledTimer(withTimeInterval: checkFrequency, repeats: true) { _ in
|
||||||
self.checkForUpdates()
|
self.checkForUpdates()
|
||||||
}
|
}
|
||||||
timer.tolerance = 60*60
|
timer.tolerance = 60*60
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Manually trigger an update check.
|
||||||
public func checkForUpdates() {
|
public func checkForUpdates() {
|
||||||
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
|
URLSession.shared.dataTask(with: Constants.updateURL) { data, _, _ in
|
||||||
guard let data = data else { return }
|
guard let data = data else { return }
|
||||||
@ -38,6 +41,8 @@ public class Updater: ObservableObject, UpdaterProtocol {
|
|||||||
}.resume()
|
}.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) {
|
public func ignore(release: Release) {
|
||||||
guard !release.critical else { return }
|
guard !release.critical else { return }
|
||||||
defaults.set(true, forKey: release.name)
|
defaults.set(true, forKey: release.name)
|
||||||
@ -50,6 +55,8 @@ public class Updater: ObservableObject, UpdaterProtocol {
|
|||||||
|
|
||||||
extension Updater {
|
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]) {
|
func evaluate(releases: [Release]) {
|
||||||
guard let release = releases
|
guard let release = releases
|
||||||
.sorted()
|
.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 {
|
func userIgnored(release: Release) -> Bool {
|
||||||
guard !release.critical else { return false }
|
guard !release.critical else { return false }
|
||||||
return defaults.bool(forKey: release.name)
|
return defaults.bool(forKey: release.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The user defaults used to store user ignore state.
|
||||||
var defaults: UserDefaults {
|
var defaults: UserDefaults {
|
||||||
UserDefaults(suiteName: "com.maxgoedjen.Secretive.updater.ignorelist")!
|
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..<body.endIndex) else { return SemVer("11.0.0") }
|
|
||||||
let numbersEnd = body.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines, options: [], range: numberStart.upperBound..<body.endIndex)?.lowerBound ?? body.endIndex
|
|
||||||
let version = numberStart.lowerBound..<numbersEnd
|
|
||||||
return SemVer(String(body[version]))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Release {
|
|
||||||
|
|
||||||
enum Constants {
|
|
||||||
static let securityContent = "Critical Security Update"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
12
Sources/Packages/Sources/Brief/UpdaterProtocol.swift
Normal file
12
Sources/Packages/Sources/Brief/UpdaterProtocol.swift
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// A protocol for retreiving the latest available version of an app.
|
||||||
|
public protocol UpdaterProtocol: ObservableObject {
|
||||||
|
|
||||||
|
/// The latest update
|
||||||
|
var update: Release? { get }
|
||||||
|
/// A boolean describing whether or not the current build of the app is a "test" build (ie, a debug build or otherwise special build)
|
||||||
|
var testBuild: Bool { get }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
# ``SecretAgentKit``
|
||||||
|
|
||||||
|
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
|
||||||
|
|
||||||
|
## Topics
|
||||||
|
|
||||||
|
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
|
||||||
|
|
||||||
|
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
|
@ -1,4 +1,4 @@
|
|||||||
# ````SecretKit````
|
# ``SecretKit``
|
||||||
|
|
||||||
SecretKit is a collection of protocols describing secrets and stores.
|
SecretKit is a collection of protocols describing secrets and stores.
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# ````SecureEnclaveSecretKit````
|
# ``SecureEnclaveSecretKit``
|
||||||
|
|
||||||
SecureEnclaveSecretKit contains implementations of SecretKit protocols backed by the Secure Enclave.
|
SecureEnclaveSecretKit contains implementations of SecretKit protocols backed by the Secure Enclave.
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# ````SmartCardSecretKit````
|
# ``SmartCardSecretKit``
|
||||||
|
|
||||||
SmartCardSecretKit contains implementations of SecretKit protocols backed by a Smart Card.
|
SmartCardSecretKit contains implementations of SecretKit protocols backed by a Smart Card.
|
||||||
|
Loading…
Reference in New Issue
Block a user