fix: align agent permissions with native app
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
import Cocoa
|
||||
import WebKit
|
||||
import ApplicationServices
|
||||
import AVFoundation
|
||||
import Network
|
||||
import UserNotifications
|
||||
|
||||
private let bossInputMonitoringTapCallback: CGEventTapCallBack = { _, _, event, _ in
|
||||
Unmanaged.passUnretained(event)
|
||||
}
|
||||
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
private var window: NSWindow?
|
||||
private var webView: WKWebView?
|
||||
private var inputMonitoringTap: CFMachPort?
|
||||
private var localNetworkBrowser: NWBrowser?
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
@@ -48,6 +58,173 @@ final class AppDelegate: NSObject, NSApplicationDelegate, WKNavigationDelegate {
|
||||
loadFallback()
|
||||
}
|
||||
|
||||
func webView(
|
||||
_ webView: WKWebView,
|
||||
decidePolicyFor navigationAction: WKNavigationAction,
|
||||
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
|
||||
) {
|
||||
guard let url = navigationAction.request.url else {
|
||||
decisionHandler(.allow)
|
||||
return
|
||||
}
|
||||
|
||||
if isPermissionSetupUrl(url) {
|
||||
decisionHandler(.cancel)
|
||||
handlePermissionSetupNavigation(url)
|
||||
return
|
||||
}
|
||||
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
private func isPermissionSetupUrl(_ url: URL) -> Bool {
|
||||
url.path == "/api/v1/boss-agent/permissions/open"
|
||||
}
|
||||
|
||||
private func handlePermissionSetupNavigation(_ url: URL) {
|
||||
let target =
|
||||
URLComponents(url: url, resolvingAgainstBaseURL: false)?
|
||||
.queryItems?
|
||||
.first(where: { $0.name == "target" })?
|
||||
.value ?? "all"
|
||||
|
||||
requestNativePermission(for: target)
|
||||
if let settingsUrl = systemSettingsUrl(for: target) {
|
||||
NSWorkspace.shared.open(settingsUrl)
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [weak self] in
|
||||
self?.loadAgentPanel()
|
||||
}
|
||||
}
|
||||
|
||||
private func requestNativePermission(for target: String) {
|
||||
let targets = target == "all"
|
||||
? [
|
||||
"accessibility",
|
||||
"screenRecording",
|
||||
"automation",
|
||||
"fullDiskAccess",
|
||||
"inputMonitoring",
|
||||
"notifications",
|
||||
"microphone",
|
||||
"camera",
|
||||
"localNetwork",
|
||||
]
|
||||
: [target]
|
||||
|
||||
for permission in targets {
|
||||
requestSingleNativePermission(permission)
|
||||
}
|
||||
}
|
||||
|
||||
private func requestSingleNativePermission(_ permission: String) {
|
||||
switch permission {
|
||||
case "accessibility":
|
||||
requestAccessibilityPermission()
|
||||
case "screenRecording":
|
||||
requestScreenRecordingPermission()
|
||||
case "automation":
|
||||
requestAutomationPermission()
|
||||
case "fullDiskAccess":
|
||||
preflightFullDiskAccess()
|
||||
case "inputMonitoring":
|
||||
requestInputMonitoringPermission()
|
||||
case "notifications":
|
||||
requestNotificationPermission()
|
||||
case "microphone":
|
||||
AVCaptureDevice.requestAccess(for: .audio) { _ in }
|
||||
case "camera":
|
||||
AVCaptureDevice.requestAccess(for: .video) { _ in }
|
||||
case "localNetwork":
|
||||
requestLocalNetworkPermission()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func requestAccessibilityPermission() {
|
||||
let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
|
||||
let options = [promptKey: true] as CFDictionary
|
||||
_ = AXIsProcessTrustedWithOptions(options)
|
||||
}
|
||||
|
||||
private func requestScreenRecordingPermission() {
|
||||
if #available(macOS 10.15, *) {
|
||||
_ = CGRequestScreenCaptureAccess()
|
||||
} else {
|
||||
_ = CGPreflightScreenCaptureAccess()
|
||||
}
|
||||
}
|
||||
|
||||
private func requestAutomationPermission() {
|
||||
let script = NSAppleScript(source: "tell application \"Finder\" to get name")
|
||||
var error: NSDictionary?
|
||||
_ = script?.executeAndReturnError(&error)
|
||||
}
|
||||
|
||||
private func preflightFullDiskAccess() {
|
||||
let candidatePaths = [
|
||||
"\(NSHomeDirectory())/Library/Safari/Bookmarks.plist",
|
||||
"\(NSHomeDirectory())/Library/Mail",
|
||||
"/Library/Application Support/com.apple.TCC/TCC.db",
|
||||
]
|
||||
for path in candidatePaths {
|
||||
if FileManager.default.fileExists(atPath: path) {
|
||||
_ = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func requestInputMonitoringPermission() {
|
||||
let eventMask = CGEventMask(1 << CGEventType.keyDown.rawValue)
|
||||
inputMonitoringTap = CGEvent.tapCreate(
|
||||
tap: .cgSessionEventTap,
|
||||
place: .headInsertEventTap,
|
||||
options: .listenOnly,
|
||||
eventsOfInterest: eventMask,
|
||||
callback: bossInputMonitoringTapCallback,
|
||||
userInfo: nil
|
||||
)
|
||||
if let tap = inputMonitoringTap {
|
||||
CFMachPortInvalidate(tap)
|
||||
inputMonitoringTap = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func requestNotificationPermission() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in }
|
||||
}
|
||||
|
||||
private func requestLocalNetworkPermission() {
|
||||
let parameters = NWParameters.tcp
|
||||
parameters.includePeerToPeer = true
|
||||
let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: parameters)
|
||||
browser.stateUpdateHandler = { _ in }
|
||||
localNetworkBrowser = browser
|
||||
browser.start(queue: .main)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
|
||||
self?.localNetworkBrowser?.cancel()
|
||||
self?.localNetworkBrowser = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func systemSettingsUrl(for target: String) -> URL? {
|
||||
let mapping = [
|
||||
"all": "x-apple.systempreferences:com.apple.preference.security?Privacy",
|
||||
"accessibility": "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility",
|
||||
"screenRecording": "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture",
|
||||
"automation": "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation",
|
||||
"fullDiskAccess": "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles",
|
||||
"inputMonitoring": "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent",
|
||||
"notifications": "x-apple.systempreferences:com.apple.Notifications-Settings.extension",
|
||||
"microphone": "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone",
|
||||
"camera": "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera",
|
||||
"localNetwork": "x-apple.systempreferences:com.apple.preference.security?Privacy_LocalNetwork",
|
||||
]
|
||||
return URL(string: mapping[target] ?? mapping["all"]!)
|
||||
}
|
||||
|
||||
private func loadFallback() {
|
||||
let html = """
|
||||
<!doctype html>
|
||||
|
||||
Reference in New Issue
Block a user