XPC scaffolding

This commit is contained in:
Max Goedjen
2022-01-02 22:15:18 -08:00
parent 98bf285ad2
commit d80d2ca656
18 changed files with 191 additions and 58 deletions

View File

@@ -15,6 +15,8 @@ struct Secretive: App {
return list
}()
private let agentStatusChecker = AgentStatusChecker()
private let agentLaunchController = AgentLaunchController()
private let agentCommunicationController = AgentCommunicationController()
private let justUpdatedChecker = JustUpdatedChecker()
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
@@ -27,6 +29,7 @@ struct Secretive: App {
.environmentObject(storeList)
.environmentObject(Updater(checkOnLaunch: hasRunSetup))
.environmentObject(agentStatusChecker)
.environmentObject(agentCommunicationController)
.onAppear {
if !hasRunSetup {
showingSetup = true
@@ -35,11 +38,17 @@ struct Secretive: App {
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
guard hasRunSetup else { return }
agentStatusChecker.check()
if agentStatusChecker.running && justUpdatedChecker.justUpdated {
// Relaunch the agent, since it'll be running from earlier update still
reinstallAgent()
} else if !agentStatusChecker.running && !agentStatusChecker.developmentBuild {
forceLaunchAgent()
if justUpdatedChecker.justUpdated || !agentStatusChecker.running {
// Two conditions in which we reinstall/attempt a force launch:
// 1: The app was just updated, and an old version of the agent is alive. Reinstall will deactivate this and activate a new one.
// 2: The agent is not running for some reason. We'll attempt to reinstall it, or relaunch directly if that fails.
reinstallAgent {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
agentCommunicationController.configure()
}
}
} else {
agentCommunicationController.configure()
}
}
}
@@ -60,6 +69,13 @@ struct Secretive: App {
showingSetup = true
}
}
CommandGroup(after: .help) {
Button("TEST") {
Task {
try await agentCommunicationController.agent.updatedStore(withID: storeList.modifiableStore?.id as? UUID ?? UUID())
}
}
}
SidebarCommands()
}
}
@@ -68,27 +84,24 @@ struct Secretive: App {
extension Secretive {
private func reinstallAgent() {
private func reinstallAgent(completion: @escaping () -> Void) {
justUpdatedChecker.check()
LaunchAgentController().install {
agentLaunchController.install {
// Wait a second for launchd to kick in (next runloop isn't enough).
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
agentStatusChecker.check()
if !agentStatusChecker.running {
forceLaunchAgent()
agentLaunchController.forceLaunch { _ in
agentStatusChecker.check()
completion()
}
} else {
completion()
}
}
}
}
private func forceLaunchAgent() {
// We've run setup, we didn't just update, launchd is just not doing it's thing.
// Force a launch directly.
LaunchAgentController().forceLaunch { _ in
agentStatusChecker.check()
}
}
}

View File

@@ -0,0 +1,36 @@
import Foundation
import Combine
import AppKit
import SecretKit
import SecretAgentKitProtocol
protocol AgentCommunicationControllerProtocol: ObservableObject {
var agent: AgentProtocol { get }
}
class AgentCommunicationController: ObservableObject, AgentCommunicationControllerProtocol {
let agent: AgentProtocol
private let connection: NSXPCConnection
private var running = false
init() {
connection = NSXPCConnection(machServiceName: Bundle.main.agentBundleID)
connection.remoteObjectInterface = NSXPCInterface(with: AgentProtocol.self)
connection.invalidationHandler = {
print("INVALID")
}
agent = connection.remoteObjectProxyWithErrorHandler({ x in
print(x)
}) as! AgentProtocol
}
func configure() {
guard !running else { return }
running = true
connection.resume()
}
}

View File

@@ -4,7 +4,7 @@ import AppKit
import OSLog
import SecretKit
struct LaunchAgentController {
struct AgentLaunchController {
func install(completion: (() -> Void)? = nil) {
Logger().debug("Installing agent")
@@ -20,7 +20,7 @@ struct LaunchAgentController {
}
func forceLaunch(completion: ((Bool) -> Void)?) {
Logger().debug("Agent is not running, attempting to force launch")
Logger().debug("Agent is still not running, attempting to force launch")
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
let config = NSWorkspace.OpenConfiguration()
config.activates = false

View File

@@ -9,6 +9,7 @@ protocol JustUpdatedCheckerProtocol: ObservableObject {
class JustUpdatedChecker: ObservableObject, JustUpdatedCheckerProtocol {
@Published var justUpdated: Bool = false
var alreadyRelaunchedForDebug = false
init() {
check()
@@ -18,7 +19,12 @@ class JustUpdatedChecker: ObservableObject, JustUpdatedCheckerProtocol {
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)
justUpdated = lastBuild != currentBuild
if currentBuild != Constants.debugVersionKey {
justUpdated = lastBuild != currentBuild
} else {
justUpdated = !alreadyRelaunchedForDebug
alreadyRelaunchedForDebug = true
}
}
@@ -29,6 +35,7 @@ extension JustUpdatedChecker {
enum Constants {
static let previousVersionUserDefaultsKey = "com.maxgoedjen.Secretive.lastBuild"
static let debugVersionKey = "GITHUB_CI_VERSION"
}
}

View File

@@ -2,6 +2,6 @@ import Foundation
extension Bundle {
public var agentBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "Host", with: "SecretAgent"))!}
public var agentBundleID: String { "Z72PRUAWF6.com.maxgoedjen.Secretive.SecretAgent" }
public var hostBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "SecretAgent", with: "Host"))!}
}

View File

@@ -45,6 +45,7 @@ extension Preview {
}
class StoreModifiable: Store, SecretStoreModifiable {
override var name: String { "Modifiable Preview Store" }
func create(name: String, requiresAuthentication: Bool) throws {
@@ -55,6 +56,10 @@ extension Preview {
func update(secret: Preview.Secret, name: String) throws {
}
func reload() throws {
}
}
}

View File

@@ -4,6 +4,10 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(TeamIdentifierPrefix)com.maxgoedjen.secretive</string>
</array>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>

View File

@@ -87,7 +87,7 @@ extension ContentView {
})
.popover(isPresented: $showingCreation, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) {
if let modifiable = storeList.modifiableStore {
CreateSecretView(store: modifiable, showing: $showingCreation)
CreateSecretView<AnySecretStoreModifiable, AgentCommunicationController>(store: modifiable, showing: $showingCreation)
}
}

View File

@@ -1,9 +1,10 @@
import SwiftUI
import SecretKit
struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
struct CreateSecretView<StoreType: SecretStoreModifiable, AgentCommunicationControllerType: AgentCommunicationControllerProtocol>: View {
@ObservedObject var store: StoreType
@EnvironmentObject private var agentCommunicationController: AgentCommunicationControllerType
@Binding var showing: Bool
@State private var name = ""
@@ -52,6 +53,9 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
func save() {
try! store.create(name: name, requiresAuthentication: requiresAuthentication)
Task {
try! await agentCommunicationController.agent.updatedStore(withID: store.id)
}
showing = false
}
}

View File

@@ -156,7 +156,7 @@ struct SecretAgentSetupView: View {
}
func install() {
LaunchAgentController().install()
AgentLaunchController().install()
buttonAction()
}