import Foundation import AppKit enum BrowserType { case safari case arc case chrome case edge case firefox case brave case opera case vivaldi case orion case zen var scriptName: String { switch self { case .safari: return "safariURL" case .arc: return "arcURL" case .chrome: return "chromeURL" case .edge: return "edgeURL" case .firefox: return "firefoxURL" case .brave: return "braveURL" case .opera: return "operaURL" case .vivaldi: return "vivaldiURL" case .orion: return "orionURL" case .zen: return "zenURL" } } var bundleIdentifier: String { switch self { case .safari: return "com.apple.Safari" case .arc: return "company.thebrowser.Browser" case .chrome: return "com.google.Chrome" case .edge: return "com.microsoft.edgemac" case .firefox: return "org.mozilla.firefox" case .brave: return "com.brave.Browser" case .opera: return "com.operasoftware.Opera" case .vivaldi: return "com.vivaldi.Vivaldi" case .orion: return "com.kagi.kagimacOS" case .zen: return "app.zen-browser.zen" } } var displayName: String { switch self { case .safari: return "Safari" case .arc: return "Arc" case .chrome: return "Google Chrome" case .edge: return "Microsoft Edge" case .firefox: return "Firefox" case .brave: return "Brave" case .opera: return "Opera" case .vivaldi: return "Vivaldi" case .orion: return "Orion" case .zen: return "Zen Browser" } } static var allCases: [BrowserType] { [.safari, .arc, .chrome, .edge, .firefox, .brave, .opera, .vivaldi, .orion, .zen] } static var installedBrowsers: [BrowserType] { allCases.filter { browser in let workspace = NSWorkspace.shared return workspace.urlForApplication(withBundleIdentifier: browser.bundleIdentifier) != nil } } } enum BrowserURLError: Error { case scriptNotFound case executionFailed case browserNotRunning case noActiveWindow case noActiveTab } class BrowserURLService { static let shared = BrowserURLService() private init() {} func getCurrentURL(from browser: BrowserType) async throws -> String { guard let scriptURL = Bundle.main.url(forResource: browser.scriptName, withExtension: "scpt") else { throw BrowserURLError.scriptNotFound } let task = Process() task.launchPath = "/usr/bin/osascript" task.arguments = [scriptURL.path] let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe do { try task.run() task.waitUntilExit() let data = pipe.fileHandleForReading.readDataToEndOfFile() if let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) { if output.isEmpty { throw BrowserURLError.noActiveTab } return output } else { throw BrowserURLError.executionFailed } } catch { throw BrowserURLError.executionFailed } } func isRunning(_ browser: BrowserType) -> Bool { let workspace = NSWorkspace.shared let runningApps = workspace.runningApplications return runningApps.contains { $0.bundleIdentifier == browser.bundleIdentifier } } }