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
6 changes: 3 additions & 3 deletions SimplyTrack/Services/Browsers/ArcBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class ArcBrowser: BaseBrowser {
/// Checks if Arc is currently in incognito mode.
/// Uses the 'incognito' property available in Arc's AppleScript interface.
/// Note: Permissions are already verified by getCurrentURL() call, so no need to re-check.
/// - Returns: true if incognito mode is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if incognito mode is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let script = """
tell application "Arc"
if (count of windows) > 0 then
Expand All @@ -56,7 +56,7 @@ class ArcBrowser: BaseBrowser {
guard let result = scriptResult.result,
let isIncognito = Bool(result.lowercased())
else {
return false
return nil
}

return isIncognito
Expand Down
6 changes: 3 additions & 3 deletions SimplyTrack/Services/Browsers/BraveBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class BraveBrowser: BaseBrowser {
/// Checks if Brave is currently in incognito mode.
/// Uses the 'mode' property available in Brave's AppleScript interface (same as Chrome).
/// Note: Permissions are already verified by getCurrentURL() call, so no need to re-check.
/// - Returns: true if incognito mode is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if incognito mode is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let script = """
tell application "Brave Browser"
if (count of windows) > 0 then
Expand All @@ -44,7 +44,7 @@ class BraveBrowser: BaseBrowser {
guard let result = scriptResult.result,
let isIncognito = Bool(result.lowercased())
else {
return false
return nil
}

return isIncognito
Expand Down
17 changes: 13 additions & 4 deletions SimplyTrack/Services/Browsers/BrowserInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ protocol BrowserInterface {
func getCurrentURL() -> String?

/// Checks if the current active tab/window is in private browsing mode.
/// - Returns: true if private browsing is detected, false otherwise
func isInPrivateBrowsingMode() -> Bool
/// - Returns: true if private browsing is detected, false if regular browsing is detected, nil if detection failed
func isInPrivateBrowsingMode() -> Bool?
}

/// Base implementation providing common AppleScript execution functionality.
/// Browser-specific classes can inherit from this to get shared AppleScript execution logic.
class BaseBrowser: BrowserInterface {
private static let appleScriptTimeoutSeconds = 3

let bundleId: String
let displayName: String

Expand All @@ -60,6 +62,8 @@ class BaseBrowser: BrowserInterface {
// Handle permission-related errors
if scriptResult.errorCode == -1743 || scriptResult.errorCode == -1744 {
PermissionManager.shared.handleBrowserPermissionResult(success: false)
} else if scriptResult.errorCode == -1712 {
logger.debug("Browser (\(self.displayName)) AppleScript timed out")
} else if scriptResult.errorCode == -1719 {
// Invalid index - transient race condition when tabs/windows change during polling.
// Silently ignore; the next polling cycle will succeed.
Expand All @@ -85,7 +89,7 @@ class BaseBrowser: BrowserInterface {
}

/// Default implementation - subclasses should override
func isInPrivateBrowsingMode() -> Bool {
func isInPrivateBrowsingMode() -> Bool? {
fatalError("Subclasses must implement isInPrivateBrowsingMode()")
}

Expand All @@ -95,7 +99,12 @@ class BaseBrowser: BrowserInterface {
/// - Returns: AppleScriptResult containing the result, error code, and error details
internal func executeAppleScript(_ script: String) -> AppleScriptResult {
var error: NSDictionary?
let appleScript = NSAppleScript(source: script)
let wrappedScript = """
with timeout of \(Self.appleScriptTimeoutSeconds) seconds
\(script)
end timeout
"""
let appleScript = NSAppleScript(source: wrappedScript)
let result = appleScript?.executeAndReturnError(&error)

let errorCode = error?["NSAppleScriptErrorNumber"] as? Int
Expand Down
6 changes: 3 additions & 3 deletions SimplyTrack/Services/Browsers/ChromeBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class ChromeBrowser: BaseBrowser {
/// Checks if Chrome is currently in incognito mode.
/// Uses the reliable 'mode' property available in Chrome's AppleScript interface.
/// Note: Permissions are already verified by getCurrentURL() call, so no need to re-check.
/// - Returns: true if incognito mode is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if incognito mode is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let script = """
tell application "Google Chrome"
if (count of windows) > 0 then
Expand All @@ -45,7 +45,7 @@ class ChromeBrowser: BaseBrowser {
guard let result = scriptResult.result,
let isIncognito = Bool(result.lowercased())
else {
return false
return nil
}

return isIncognito
Expand Down
6 changes: 3 additions & 3 deletions SimplyTrack/Services/Browsers/EdgeBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class EdgeBrowser: BaseBrowser {
/// Checks if Edge is currently in InPrivate mode.
/// Uses the 'mode' property similar to Chrome, since Edge is Chromium-based.
/// Note: Permissions are already verified by getCurrentURL() call, so no need to re-check.
/// - Returns: true if InPrivate mode is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if InPrivate mode is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let script = """
tell application "Microsoft Edge"
if (count of windows) > 0 then
Expand All @@ -45,7 +45,7 @@ class EdgeBrowser: BaseBrowser {
guard let result = scriptResult.result,
let isInPrivate = Bool(result.lowercased())
else {
return false
return nil
}

return isInPrivate
Expand Down
8 changes: 5 additions & 3 deletions SimplyTrack/Services/Browsers/FirefoxBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class FirefoxBrowser: BaseBrowser {
PermissionManager.shared.handleBrowserError(
"Firefox requires additional setup for website tracking: open about:config in Firefox and set accessibility.force_disabled to -1."
)
} else if scriptResult.errorCode == -1712 {
logger.debug("Firefox Accessibility AppleScript timed out")
} else if scriptResult.errorCode == -1743 || scriptResult.errorCode == -1744 {
PermissionManager.shared.handleSystemEventsPermissionResult(success: false)
} else if scriptResult.errorCode == -1719 || scriptResult.errorCode == -1728 {
Expand Down Expand Up @@ -126,16 +128,16 @@ class FirefoxBrowser: BaseBrowser {

/// Checks if Firefox is currently in private browsing mode.
/// Firefox appends a localized private browsing indicator to the window title.
/// - Returns: true if private browsing is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if private browsing is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let script = """
tell application "Firefox" to return name of front window
"""

let scriptResult = executeAppleScript(script)

guard let windowName = scriptResult.result else {
return false
return nil
}

return windowName.hasSuffix(Self.privateBrowsingSuffix)
Expand Down
13 changes: 8 additions & 5 deletions SimplyTrack/Services/Browsers/SafariBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class SafariBrowser: BaseBrowser {

/// Checks if Safari is currently in private browsing mode.
/// Uses System Events to check for private window menu item.
/// - Returns: true if private browsing is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if private browsing is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let systemEventsScript = """
tell application "System Events"
tell process "Safari"
Expand All @@ -53,15 +53,18 @@ class SafariBrowser: BaseBrowser {
// Invalid index - transient race condition when menu items change during polling.
// Silently ignore; the next polling cycle will succeed.
logger.debug("Safari System Events transient error (invalid index): \(error.description)")
return false
return nil
} else if scriptResult.errorCode == -1712 {
logger.debug("Safari System Events AppleScript timed out")
return nil
} else if scriptResult.errorCode == -1743 || scriptResult.errorCode == -1744 {
// System Events permission errors
PermissionManager.shared.handleSystemEventsPermissionResult(success: false)
} else {
// Log non-permission System Events errors
logger.error("Safari System Events AppleScript error: \(error.description)")
}
return false
return nil
}

// If we successfully executed System Events AppleScript, permissions are working
Expand All @@ -74,6 +77,6 @@ class SafariBrowser: BaseBrowser {
return isPrivate
}

return false
return nil
}
}
6 changes: 3 additions & 3 deletions SimplyTrack/Services/Browsers/VivaldiBrowser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class VivaldiBrowser: BaseBrowser {
/// Checks if Vivaldi is currently in private mode.
/// Uses the 'mode' property available in Chromium-based browser AppleScript interfaces.
/// Note: Permissions are already verified by getCurrentURL() call, so no need to re-check.
/// - Returns: true if private mode is detected, false otherwise
override func isInPrivateBrowsingMode() -> Bool {
/// - Returns: true if private mode is detected, false if regular browsing is detected, nil if detection failed
override func isInPrivateBrowsingMode() -> Bool? {
let script = """
tell application id "com.vivaldi.Vivaldi"
if (count of windows) > 0 then
Expand All @@ -44,7 +44,7 @@ class VivaldiBrowser: BaseBrowser {
guard let result = scriptResult.result,
let isPrivate = Bool(result.lowercased())
else {
return false
return nil
}

return isPrivate
Expand Down
Loading
Loading