mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-07-01 09:43:37 +00:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
cd0a1b0a68 | |||
8bbf489146 | |||
30148ee3a4 | |||
c306801149 |
@ -4,6 +4,7 @@ import Combine
|
|||||||
public protocol UpdaterProtocol: ObservableObject {
|
public protocol UpdaterProtocol: ObservableObject {
|
||||||
|
|
||||||
var update: Release? { get }
|
var update: Release? { get }
|
||||||
|
func ignore(release: Release)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,25 +42,14 @@ extension Updater {
|
|||||||
|
|
||||||
func evaluate(release: Release) {
|
func evaluate(release: Release) {
|
||||||
guard !userIgnored(release: release) else { return }
|
guard !userIgnored(release: release) else { return }
|
||||||
let latestVersion = semVer(from: release.name)
|
let latestVersion = SemVer(release.name)
|
||||||
let currentVersion = semVer(from: Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String)
|
let currentVersion = SemVer(Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String)
|
||||||
for (latest, current) in zip(latestVersion, currentVersion) {
|
if latestVersion > currentVersion {
|
||||||
if latest > current {
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.update = release
|
self.update = release
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func semVer(from stringVersion: String) -> [Int] {
|
|
||||||
var split = stringVersion.split(separator: ".").compactMap { Int($0) }
|
|
||||||
while split.count < 3 {
|
|
||||||
split.append(0)
|
|
||||||
}
|
|
||||||
return split
|
|
||||||
}
|
|
||||||
|
|
||||||
func userIgnored(release: Release) -> Bool {
|
func userIgnored(release: Release) -> Bool {
|
||||||
guard !release.critical else { return false }
|
guard !release.critical else { return false }
|
||||||
@ -71,6 +61,38 @@ extension Updater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SemVer {
|
||||||
|
|
||||||
|
let versionNumbers: [Int]
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SemVer: Comparable {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension Updater {
|
extension Updater {
|
||||||
|
|
||||||
enum Constants {
|
enum Constants {
|
||||||
@ -93,11 +115,18 @@ public struct Release: Codable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Release: Identifiable {
|
||||||
|
|
||||||
|
public var id: String {
|
||||||
|
html_url.absoluteString
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
extension Release {
|
extension Release {
|
||||||
|
|
||||||
public var critical: Bool {
|
public var critical: Bool {
|
||||||
return body.contains(Constants.securityContent)
|
body.contains(Constants.securityContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class Notifier {
|
|||||||
let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: "Update", options: [])
|
let updateAction = UNNotificationAction(identifier: Constants.updateActionIdentitifier, title: "Update", options: [])
|
||||||
let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: "Ignore", options: [])
|
let ignoreAction = UNNotificationAction(identifier: Constants.ignoreActionIdentitifier, title: "Ignore", options: [])
|
||||||
let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: [])
|
let updateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction, ignoreAction], intentIdentifiers: [], options: [])
|
||||||
let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.updateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: [])
|
let criticalUpdateCategory = UNNotificationCategory(identifier: Constants.criticalUpdateCategoryIdentitifier, actions: [updateAction], intentIdentifiers: [], options: [])
|
||||||
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory])
|
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory])
|
||||||
UNUserNotificationCenter.current().delegate = notificationDelegate
|
UNUserNotificationCenter.current().delegate = notificationDelegate
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||||
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
||||||
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; };
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||||
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* AppDelegate.swift */; };
|
50617D8323FCE48E0099B055 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* AppDelegate.swift */; };
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
||||||
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||||
@ -206,6 +208,8 @@
|
|||||||
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
||||||
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
||||||
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||||
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentController.swift; sourceTree = "<group>"; };
|
||||||
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
50617D8223FCE48E0099B055 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50617D8223FCE48E0099B055 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
@ -512,6 +516,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */,
|
||||||
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */,
|
||||||
|
50571E0424393D1500F76F6C /* LaunchAgentController.swift */,
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -916,10 +922,12 @@
|
|||||||
files = (
|
files = (
|
||||||
50C385A9240B636500AF2719 /* SetupView.swift in Sources */,
|
50C385A9240B636500AF2719 /* SetupView.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
||||||
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */,
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */,
|
||||||
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */,
|
||||||
|
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */,
|
||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */,
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
50731669241E00C20023809E /* NoticeView.swift in Sources */,
|
50731669241E00C20023809E /* NoticeView.swift in Sources */,
|
||||||
|
@ -17,6 +17,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}()
|
}()
|
||||||
let updater = Updater()
|
let updater = Updater()
|
||||||
let agentStatusChecker = AgentStatusChecker()
|
let agentStatusChecker = AgentStatusChecker()
|
||||||
|
let justUpdatedChecker = JustUpdatedChecker()
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
let contentView = ContentView(storeList: storeList, updater: updater, agentStatusChecker: agentStatusChecker, runSetupBlock: { self.runSetup(sender: nil) })
|
let contentView = ContentView(storeList: storeList, updater: updater, agentStatusChecker: agentStatusChecker, runSetupBlock: { self.runSetup(sender: nil) })
|
||||||
@ -40,6 +41,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
newMenuItem.isEnabled = true
|
newMenuItem.isEnabled = true
|
||||||
}
|
}
|
||||||
runSetupIfNeeded()
|
runSetupIfNeeded()
|
||||||
|
relaunchAgentIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidBecomeActive(_ notification: Notification) {
|
func applicationDidBecomeActive(_ notification: Notification) {
|
||||||
@ -89,6 +91,12 @@ extension AppDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func relaunchAgentIfNeeded() {
|
||||||
|
if agentStatusChecker.running && justUpdatedChecker.justUpdated {
|
||||||
|
LaunchAgentController().relaunch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppDelegate {
|
extension AppDelegate {
|
||||||
|
36
Secretive/Controllers/JustUpdatedChecker.swift
Normal file
36
Secretive/Controllers/JustUpdatedChecker.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
protocol JustUpdatedCheckerProtocol: ObservableObject {
|
||||||
|
var justUpdated: Bool { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
class JustUpdatedChecker: ObservableObject, JustUpdatedCheckerProtocol {
|
||||||
|
|
||||||
|
@Published var justUpdated: Bool = false
|
||||||
|
|
||||||
|
init() {
|
||||||
|
check()
|
||||||
|
}
|
||||||
|
|
||||||
|
func check() {
|
||||||
|
let lastBuild = UserDefaults.standard.object(forKey: Constants.previousVersionUserDefaultsKey) as? String ?? "None"
|
||||||
|
let currentBuild = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
|
||||||
|
UserDefaults.standard.set(currentBuild, forKey: Constants.previousVersionUserDefaultsKey)
|
||||||
|
if lastBuild != currentBuild {
|
||||||
|
justUpdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JustUpdatedChecker {
|
||||||
|
|
||||||
|
enum Constants {
|
||||||
|
static let previousVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastBuild"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
19
Secretive/Controllers/LaunchAgentController.swift
Normal file
19
Secretive/Controllers/LaunchAgentController.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Foundation
|
||||||
|
import ServiceManagement
|
||||||
|
|
||||||
|
struct LaunchAgentController {
|
||||||
|
|
||||||
|
func install() -> Bool {
|
||||||
|
setEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func relaunch() {
|
||||||
|
_ = setEnabled(false)
|
||||||
|
_ = setEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||||
|
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,6 +17,8 @@ class PreviewUpdater: UpdaterProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ignore(release: Release) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreviewUpdater {
|
extension PreviewUpdater {
|
||||||
|
@ -87,9 +87,13 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
|
|||||||
severity = .advisory
|
severity = .advisory
|
||||||
text = "Update Available"
|
text = "Update Available"
|
||||||
}
|
}
|
||||||
return AnyView(NoticeView(text: text, severity: severity, actionTitle: "Update") {
|
let action = {
|
||||||
NSWorkspace.shared.open(update.html_url)
|
_ = NSWorkspace.shared.open(update.html_url)
|
||||||
})
|
}
|
||||||
|
let ignoreAction = {
|
||||||
|
updater.ignore(release: update)
|
||||||
|
}
|
||||||
|
return AnyView(NoticeView(text: text, severity: severity, actionTitle: "Update", action: action, secondaryActionTitle: "Ignore", secondaryAction: ignoreAction))
|
||||||
}
|
}
|
||||||
|
|
||||||
func agentNotice() -> some View {
|
func agentNotice() -> some View {
|
||||||
|
@ -7,12 +7,28 @@ struct NoticeView: View {
|
|||||||
let severity: Severity
|
let severity: Severity
|
||||||
let actionTitle: String?
|
let actionTitle: String?
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
|
let secondaryActionTitle: String?
|
||||||
|
let secondaryAction: (() -> Void)?
|
||||||
|
|
||||||
|
public init(text: String, severity: NoticeView.Severity, actionTitle: String?, action: (() -> Void)?, secondaryActionTitle: String? = nil, secondaryAction: (() -> Void)? = nil) {
|
||||||
|
self.text = text
|
||||||
|
self.severity = severity
|
||||||
|
self.actionTitle = actionTitle
|
||||||
|
self.action = action
|
||||||
|
self.secondaryActionTitle = secondaryActionTitle
|
||||||
|
self.secondaryAction = secondaryAction
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text(text).bold()
|
Text(text).bold()
|
||||||
Spacer()
|
Spacer()
|
||||||
if action != nil {
|
if action != nil {
|
||||||
|
if secondaryAction != nil {
|
||||||
|
Button(action: secondaryAction!) {
|
||||||
|
Text(secondaryActionTitle!)
|
||||||
|
}
|
||||||
|
}
|
||||||
Button(action: action!) {
|
Button(action: action!) {
|
||||||
Text(actionTitle!)
|
Text(actionTitle!)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ServiceManagement
|
|
||||||
|
|
||||||
struct SetupView: View {
|
struct SetupView: View {
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ struct SetupStepCommandView: View {
|
|||||||
extension SetupView {
|
extension SetupView {
|
||||||
|
|
||||||
func installLaunchAgent() -> Bool {
|
func installLaunchAgent() -> Bool {
|
||||||
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, true)
|
LaunchAgentController().install()
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAsDone() -> Bool {
|
func markAsDone() -> Bool {
|
||||||
|
Reference in New Issue
Block a user