Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ body:
- Arc
- Brave
- Vivaldi
- Dia
- Firefox (experimental)

validations:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ SimplyTrack requires several macOS permissions to function properly:

1. **Automation Permission**: To track browser activity
- System Preferences → Privacy & Security → Automation
- Enable SimplyTrack for your browsers (Safari, Chrome, Edge, Arc, Brave, Vivaldi, Firefox)
- Enable SimplyTrack for your browsers (Safari, Chrome, Edge, Arc, Brave, Vivaldi, Dia, Firefox)

2. **System Events Permission**: For Safari and Firefox browser integration
- System Preferences → Privacy & Security → Automation
Expand Down
3 changes: 3 additions & 0 deletions SimplyTrack/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
// Initialize all services with proper dependency injection
initializeServices()

// Prompt for accessibility permission if not already granted
PermissionManager.shared.checkAccessibilityPermission()

// Start services in correct order
menuBarManager?.setupMenuBar()
trackingService?.startTracking()
Expand Down
22 changes: 22 additions & 0 deletions SimplyTrack/Managers/PermissionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import AppKit
import ApplicationServices
import Foundation
import os.log

/// Status of macOS system permissions required for app functionality.
/// Used to track automation permissions needed for browser integration.
Expand All @@ -27,6 +28,8 @@ class PermissionManager: ObservableObject {
/// Shared singleton instance for permission management
static let shared = PermissionManager()

private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PermissionManager")

/// Current status of automation permissions for browser AppleScript access
@Published var automationPermissionStatus: PermissionStatus = .notDetermined
/// Current status of System Events automation permissions (needed for Safari private browsing detection)
Expand All @@ -43,6 +46,7 @@ class PermissionManager: ObservableObject {
"company.thebrowser.Browser",
"com.brave.Browser",
"com.vivaldi.Vivaldi",
"company.thebrowser.dia",
"org.mozilla.firefox",
]

Expand Down Expand Up @@ -89,6 +93,24 @@ class PermissionManager: ObservableObject {
}
}

/// Checks if accessibility permissions are granted and prompts if not.
/// Should be called at app startup to trigger the macOS permission dialog.
/// Does not set `.denied` on failure — the browser error paths handle that
/// once the user has had a chance to respond to the system prompt.
func checkAccessibilityPermission() {
let trusted = AXIsProcessTrustedWithOptions(
[kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
)
if trusted {
logger.info("Accessibility permission already granted")
Task { @MainActor in
self.accessibilityPermissionStatus = .granted
}
} else {
logger.warning("Accessibility permission not granted — user prompted")
}
}

/// Opens System Preferences to the Automation privacy settings.
/// Allows users to grant AppleScript permissions for browser automation and System Events access.
func openSystemPreferences() {
Expand Down
71 changes: 71 additions & 0 deletions SimplyTrack/Services/Browsers/DiaBrowser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// DiaBrowser.swift
// SimplyTrack
//

import Foundation
import os.log

/// Dia-specific implementation of browser interface.
/// Handles URL detection for the Dia browser by The Browser Company.
/// Dia exposes a custom AppleScript interface with tab and URL support.
class DiaBrowser: BaseBrowser {
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "DiaBrowser")

init() {
super.init(bundleId: "company.thebrowser.dia", displayName: "Dia")
}

/// Dia-specific AppleScript for URL retrieval
override var currentURLScript: String {
return """
tell application "Dia"
if (count of windows) > 0 then
return URL of active tab of window 1
end if
end tell
"""
}

/// Checks if Dia is currently in incognito mode.
/// Dia doesn't expose a private browsing property in its AppleScript dictionary,
/// so we use the accessibility API to check the window's AXIdentifier which
/// contains "bigIncognitoBrowserWindow" for private windows.
override func isInPrivateBrowsingMode() -> Bool {
let script = """
tell application "System Events"
tell process "Dia"
if (count of windows) > 0 then
return value of attribute "AXIdentifier" of front window
end if
end tell
end tell
"""

let scriptResult = executeAppleScript(script)

if let error = scriptResult.error {
if scriptResult.errorCode == -1719 {
logger.debug("Dia System Events transient error (invalid index): \(error.description)")
} else if scriptResult.errorCode == -1743 || scriptResult.errorCode == -1744 {
PermissionManager.shared.handleSystemEventsPermissionResult(success: false)
} else if scriptResult.errorCode == -25211 {
PermissionManager.shared.handleAccessibilityPermissionResult(success: false)
} else {
logger.error("Dia System Events AppleScript error: \(error.description)")
}
return false
}

if scriptResult.result != nil {
PermissionManager.shared.handleSystemEventsPermissionResult(success: true)
PermissionManager.shared.handleAccessibilityPermissionResult(success: true)
}

guard let identifier = scriptResult.result else {
return false
}

return identifier.contains("Incognito")
}
}
2 changes: 2 additions & 0 deletions SimplyTrack/Services/Browsers/FirefoxBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class FirefoxBrowser: BaseBrowser {
)
} else if scriptResult.errorCode == -1743 || scriptResult.errorCode == -1744 {
PermissionManager.shared.handleSystemEventsPermissionResult(success: false)
} else if scriptResult.errorCode == -25211 {
PermissionManager.shared.handleAccessibilityPermissionResult(success: false)
} else if scriptResult.errorCode == -1719 || scriptResult.errorCode == -1728 {
// Firefox's accessibility hierarchy can shift during navigation or between versions.
logger.debug("Firefox address bar unavailable in current accessibility tree: \(error.description)")
Expand Down
2 changes: 2 additions & 0 deletions SimplyTrack/Services/Browsers/SafariBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class SafariBrowser: BaseBrowser {
} else if scriptResult.errorCode == -1743 || scriptResult.errorCode == -1744 {
// System Events permission errors
PermissionManager.shared.handleSystemEventsPermissionResult(success: false)
} else if scriptResult.errorCode == -25211 {
PermissionManager.shared.handleAccessibilityPermissionResult(success: false)
} else {
// Log non-permission System Events errors
logger.error("Safari System Events AppleScript error: \(error.description)")
Expand Down
3 changes: 2 additions & 1 deletion SimplyTrack/Services/WebTrackingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SimplyTrack
//
// Handles browser integration, AppleScript execution, favicon fetching, and website detection
// Supports Safari, Chrome, Edge, Arc, Brave, Vivaldi, and Firefox through AppleScript communication for website tracking
// Supports Safari, Chrome, Edge, Arc, Brave, Vivaldi, Dia, and Firefox through AppleScript communication for website tracking
//

import AppKit
Expand Down Expand Up @@ -63,6 +63,7 @@ class WebTrackingService {
ArcBrowser(),
BraveBrowser(),
VivaldiBrowser(),
DiaBrowser(),
FirefoxBrowser(),
].reduce(into: [:]) { result, browser in
result[browser.bundleId] = browser
Expand Down
1 change: 1 addition & 0 deletions SimplyTrack/SimplyTrack.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<string>company.thebrowser.Browser</string>
<string>com.brave.Browser</string>
<string>com.vivaldi.Vivaldi</string>
<string>company.thebrowser.dia</string>
<string>org.mozilla.firefox</string>
<string>com.apple.systemevents</string>
</array>
Expand Down
4 changes: 2 additions & 2 deletions SimplyTrack/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ struct ContentView: View {
if permissionManager.systemEventsPermissionStatus == .denied {
PermissionBannerView(
title: "System Events Permission Required",
message: "SimplyTrack needs System Events access to detect Safari private browsing. Enable it in System Preferences.",
message: "SimplyTrack needs System Events access to detect Safari and Dia private browsing. Enable it in System Preferences.",
primaryButtonTitle: "Open System Preferences",
primaryAction: { permissionManager.openSystemPreferences() },
color: .orange
Expand All @@ -133,7 +133,7 @@ struct ContentView: View {
if permissionManager.accessibilityPermissionStatus == .denied {
PermissionBannerView(
title: "Accessibility Permission Required",
message: "SimplyTrack needs Accessibility access to detect Safari private browsing. Enable it in System Preferences.",
message: "SimplyTrack needs Accessibility access to detect Safari and Dia private browsing. Enable it in System Preferences.",
primaryButtonTitle: "Open System Preferences",
primaryAction: { permissionManager.openAccessibilityPreferences() },
color: .orange
Expand Down
10 changes: 10 additions & 0 deletions SimplyTrack/Views/Settings/PrivacySettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ struct PrivacySettingsView: View {
.foregroundColor(.secondary)
}

HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Dia Incognito")
Spacer()
Text("Supported")
.font(.caption)
.foregroundColor(.secondary)
}

HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Expand Down
Loading