Add a real permission prompt/status for Mail access, matching Personal Data
Settings previously only surfaced an error message after a failed Test Connection attempt — there was no way to trigger the actual macOS Automation permission dialog or see live grant/deny status, unlike Calendar/Contacts/Reminders/Location. AEDeterminePermissionToAutomateTarget (askUserIfNeeded: false/true) turns out to provide exactly that: a non-prompting status check and an explicit prompt-and-wait call, mirroring EKEventStore.authorizationStatus(for:)/ requestFullAccessToEvents(). Mail's Settings row now uses the same personalDataRow component as Calendar/Contacts — live status badge plus a real "Request Access" button — instead of a one-off Test-Connection-only UI. Test Connection stays as a secondary functional check.
This commit is contained in:
@@ -53,6 +53,51 @@ final class AppleMailService {
|
||||
// through one queue, mirroring MCPService.runBashCommand's off-main-actor pattern.
|
||||
private let queue = DispatchQueue(label: "com.oai.applemail", qos: .userInitiated)
|
||||
|
||||
// MARK: - Automation Permission (Settings UI status badge + Request Access button)
|
||||
|
||||
/// Live tri-state permission check via `AEDeterminePermissionToAutomateTarget`, which — unlike
|
||||
/// sending an actual Apple Event — can query Automation permission for Mail.app without
|
||||
/// triggering any script execution or the system prompt (`askUserIfNeeded: false`). This is
|
||||
/// the Apple-Events equivalent of `EKEventStore.authorizationStatus(for:)`, reusing the same
|
||||
/// `PersonalDataAccessState` enum EventKitService/ContactsService/LocationMapsService use.
|
||||
var accessState: PersonalDataAccessState {
|
||||
Self.mapAutomationPermissionStatus(Self.checkMailAutomationPermission(askUserIfNeeded: false))
|
||||
}
|
||||
|
||||
/// Pure mapping from an `AEDeterminePermissionToAutomateTarget` status code to the shared
|
||||
/// tri-state enum, kept separate from the live AE call for testability.
|
||||
nonisolated static func mapAutomationPermissionStatus(_ status: OSStatus) -> PersonalDataAccessState {
|
||||
switch status {
|
||||
case 0: return .granted // noErr
|
||||
case -1743: return .denied // errAEEventNotPermitted
|
||||
default: return .notDetermined // errAEEventWouldRequireUserConsent (-1744) and others
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers the real macOS "Confab wants to control Mail" Automation permission prompt if
|
||||
/// the user hasn't been asked yet (a no-op if already granted or denied). Blocks until the
|
||||
/// user responds, so this must run off the main thread.
|
||||
@discardableResult
|
||||
func requestAccess() async -> Bool {
|
||||
await withCheckedContinuation { continuation in
|
||||
queue.async {
|
||||
let status = Self.checkMailAutomationPermission(askUserIfNeeded: true)
|
||||
continuation.resume(returning: status == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func checkMailAutomationPermission(askUserIfNeeded: Bool) -> OSStatus {
|
||||
let bundleID = "com.apple.mail"
|
||||
var target = AEAddressDesc()
|
||||
let createStatus = bundleID.withCString { cString in
|
||||
AECreateDesc(typeApplicationBundleID, cString, bundleID.utf8.count, &target)
|
||||
}
|
||||
guard createStatus == 0 else { return OSStatus(createStatus) }
|
||||
defer { AEDisposeDesc(&target) }
|
||||
return AEDeterminePermissionToAutomateTarget(&target, typeWildCard, typeWildCard, askUserIfNeeded)
|
||||
}
|
||||
|
||||
@ObservationIgnored
|
||||
private lazy var compiledScript: NSAppleScript? = {
|
||||
guard let script = NSAppleScript(source: Self.scriptSource) else { return nil }
|
||||
|
||||
Reference in New Issue
Block a user