From fbc323455e533a4417f0ef60359d4d3898ca8451 Mon Sep 17 00:00:00 2001 From: Maxim Borzov Date: Wed, 18 Mar 2026 08:43:52 +0300 Subject: [PATCH] chore: bump version to 0.8.2 --- CHANGELOG.md | 18 ++++++++++++++++++ Casks/vpn-bar.rb | 2 +- Scripts/package_app.sh | 13 +------------ Sources/VPNBarApp/AppConstants.swift | 2 +- Sources/VPNBarApp/AppDelegate.swift | 2 ++ Sources/VPNBarApp/HotkeyManager.swift | 1 + .../Managers/NetworkInfoManager.swift | 12 ++++++------ .../Managers/VPNConfigurationLoader.swift | 2 +- .../VPNBarApp/Models/ConnectionHistory.swift | 10 ++-------- Sources/VPNBarApp/Models/KeyCode.swift | 2 ++ Sources/VPNBarApp/Models/VPNStatistics.swift | 6 ++++++ Sources/VPNBarApp/StatusBarController.swift | 5 +---- Sources/VPNBarApp/VPNManager.swift | 1 + 13 files changed, 43 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd2474..9ed1e99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), --- +## [0.8.2] - 2026-03-18 + +### Fixed +- Switched geolocation provider from HTTP to HTTPS to prevent IP address and location data from being transmitted in plain text +- Fixed observer leak in AppDelegate — notification observers are now properly removed on app termination +- Fixed VPN session statistics being lost when the app quits while a connection is active — duration is now recorded before shutdown +- Fixed potential use-after-deallocation in StatusBarController by removing unnecessary async dispatch in deinit +- Fixed double-cleanup crash risk in HotkeyManager when cleanup is called both from app termination and deinit +- Fixed double observer removal in VPNManager by properly nullifying the observer token in deinit +- Fixed VPN configuration loader silently returning empty results on recursive loading — it now reports a proper error +- Added logging for unknown key codes to aid debugging when hotkey registration uses unexpected values + +### Changed +- Removed unnecessary App Transport Security exception for ip-api.com since the new provider uses HTTPS +- Removed redundant dirty-tracking flag in connection history that served no practical purpose + +--- + ## [0.8.1] - 2026-02-22 ### Fixed diff --git a/Casks/vpn-bar.rb b/Casks/vpn-bar.rb index 8f90749..af0003c 100644 --- a/Casks/vpn-bar.rb +++ b/Casks/vpn-bar.rb @@ -1,5 +1,5 @@ cask "vpn-bar" do - version "0.8.1" + version "0.8.2" sha256 "b6a4ca9f37b43d57db32283e3537612633a639d7051d0e9c96b59eddd92a87f8" url "https://github.com/borzov/vpn-bar/releases/download/v#{version}/VPNBarApp.zip" diff --git a/Scripts/package_app.sh b/Scripts/package_app.sh index c4eb55d..b4868be 100755 --- a/Scripts/package_app.sh +++ b/Scripts/package_app.sh @@ -117,7 +117,7 @@ cat > "$CONTENTS_DIR/Info.plist" <CFBundlePackageType APPL CFBundleShortVersionString - 0.8.1 + 0.8.2 CFBundleVersion 1 CFBundleLocalizations @@ -136,17 +136,6 @@ cat > "$CONTENTS_DIR/Info.plist" <VPNBarApp NSUserNotificationAlertStyle banner - NSAppTransportSecurity - - NSExceptionDomains - - ip-api.com - - NSExceptionAllowsInsecureHTTPLoads - - - - EOF diff --git a/Sources/VPNBarApp/AppConstants.swift b/Sources/VPNBarApp/AppConstants.swift index 7997753..acbbb6e 100644 --- a/Sources/VPNBarApp/AppConstants.swift +++ b/Sources/VPNBarApp/AppConstants.swift @@ -43,7 +43,7 @@ enum AppConstants { /// Network info related constants. enum NetworkInfo { - static let geoIPURL = URL(string: "http://ip-api.com/json/?fields=status,country,countryCode,city,query")! + static let geoIPURL = URL(string: "https://ipwho.is/?fields=success,country,country_code,city,ip")! static let requestTimeout: TimeInterval = 10.0 } diff --git a/Sources/VPNBarApp/AppDelegate.swift b/Sources/VPNBarApp/AppDelegate.swift index 499bd6e..4b95ab6 100644 --- a/Sources/VPNBarApp/AppDelegate.swift +++ b/Sources/VPNBarApp/AppDelegate.swift @@ -72,6 +72,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ notification: Notification) { + NotificationCenter.default.removeObserver(self) + StatisticsManager.shared.recordPendingSession() HotkeyManager.shared.cleanup() Task { @MainActor in VPNManager.shared.cleanup() diff --git a/Sources/VPNBarApp/HotkeyManager.swift b/Sources/VPNBarApp/HotkeyManager.swift index e444ff9..8d5c3e7 100644 --- a/Sources/VPNBarApp/HotkeyManager.swift +++ b/Sources/VPNBarApp/HotkeyManager.swift @@ -196,6 +196,7 @@ class HotkeyManager: HotkeyManagerProtocol { /// Explicitly cleans up all resources. Should be called when the application terminates. func cleanup() { + guard isValid else { return } Logger.hotkey.info("Cleaning up hotkey manager") isValid = false diff --git a/Sources/VPNBarApp/Managers/NetworkInfoManager.swift b/Sources/VPNBarApp/Managers/NetworkInfoManager.swift index a746ab5..d120bd8 100644 --- a/Sources/VPNBarApp/Managers/NetworkInfoManager.swift +++ b/Sources/VPNBarApp/Managers/NetworkInfoManager.swift @@ -93,11 +93,11 @@ final class NetworkInfoManager: NetworkInfoManagerProtocol { // MARK: - GeoIP private struct GeoIPResponse: Decodable { - let status: String? + let success: Bool? let country: String? - let countryCode: String? + let country_code: String? let city: String? - let query: String? + let ip: String? } private struct GeoInfo { @@ -120,14 +120,14 @@ final class NetworkInfoManager: NetworkInfoManagerProtocol { } let decoded = try JSONDecoder().decode(GeoIPResponse.self, from: data) - guard decoded.status == "success" else { + guard decoded.success == true else { return nil } let geoInfo = GeoInfo( - ip: decoded.query, + ip: decoded.ip, country: decoded.country, - countryCode: decoded.countryCode, + countryCode: decoded.country_code, city: decoded.city ) let hasAnyGeoData = geoInfo.ip != nil || geoInfo.country != nil || geoInfo.countryCode != nil || geoInfo.city != nil diff --git a/Sources/VPNBarApp/Managers/VPNConfigurationLoader.swift b/Sources/VPNBarApp/Managers/VPNConfigurationLoader.swift index 9a19f79..2619cc3 100644 --- a/Sources/VPNBarApp/Managers/VPNConfigurationLoader.swift +++ b/Sources/VPNBarApp/Managers/VPNConfigurationLoader.swift @@ -29,7 +29,7 @@ final class VPNConfigurationLoader: VPNConfigurationLoaderProtocol { // Prevent infinite recursion guard !isLoadingAlternative else { Logger.vpn.warning("Preventing recursive call to loadConfigurationsAlternative") - completion(.success([])) + completion(.failure(.frameworkLoadFailed(reason: "Recursive configuration loading detected"))) return } diff --git a/Sources/VPNBarApp/Models/ConnectionHistory.swift b/Sources/VPNBarApp/Models/ConnectionHistory.swift index 2c7a3de..26a0244 100644 --- a/Sources/VPNBarApp/Models/ConnectionHistory.swift +++ b/Sources/VPNBarApp/Models/ConnectionHistory.swift @@ -25,7 +25,6 @@ final class ConnectionHistoryManager { /// In-memory cache to avoid frequent UserDefaults I/O. private var cachedHistory: [ConnectionHistoryEntry]? - private var isDirty = false private init() { loadHistoryFromUserDefaults() @@ -43,11 +42,10 @@ final class ConnectionHistoryManager { /// Saves history from cache to UserDefaults. private func saveHistoryToUserDefaults() { - guard isDirty, let history = cachedHistory else { return } - + guard let history = cachedHistory else { return } + if let data = try? JSONEncoder().encode(history) { userDefaults.set(data, forKey: historyKey) - isDirty = false } } @@ -97,16 +95,12 @@ final class ConnectionHistoryManager { } cachedHistory = history - isDirty = true - - // Save immediately for data persistence saveHistoryToUserDefaults() } /// Clears connection history. func clearHistory() { cachedHistory = [] - isDirty = true userDefaults.removeObject(forKey: historyKey) } } diff --git a/Sources/VPNBarApp/Models/KeyCode.swift b/Sources/VPNBarApp/Models/KeyCode.swift index 91de3e2..5413b2b 100644 --- a/Sources/VPNBarApp/Models/KeyCode.swift +++ b/Sources/VPNBarApp/Models/KeyCode.swift @@ -1,4 +1,5 @@ import Carbon +import os.log /// Key code constants for use in the application. enum KeyCode: UInt32 { @@ -24,6 +25,7 @@ enum KeyCode: UInt32 { if let keyCode = KeyCode(rawValue: rawValue) { self = keyCode } else { + Logger.hotkey.warning("Unknown key code: \(rawValue), defaulting to .a") self = .a } } diff --git a/Sources/VPNBarApp/Models/VPNStatistics.swift b/Sources/VPNBarApp/Models/VPNStatistics.swift index 0cfbe8a..a831cf5 100644 --- a/Sources/VPNBarApp/Models/VPNStatistics.swift +++ b/Sources/VPNBarApp/Models/VPNStatistics.swift @@ -77,6 +77,12 @@ final class StatisticsManager { currentSessionStart = nil } + /// Flushes any in-progress session before shutdown. + func recordPendingSession() { + guard currentSessionStart != nil else { return } + recordDisconnection() + } + /// Resets statistics. func resetStatistics() { cachedStatistics = VPNStatistics() diff --git a/Sources/VPNBarApp/StatusBarController.swift b/Sources/VPNBarApp/StatusBarController.swift index 52a333f..fedae42 100644 --- a/Sources/VPNBarApp/StatusBarController.swift +++ b/Sources/VPNBarApp/StatusBarController.swift @@ -218,11 +218,8 @@ class StatusBarController { } deinit { - let timer = connectingAnimationTimer + connectingAnimationTimer?.invalidate() connectingAnimationTimer = nil - if let t = timer { - DispatchQueue.main.async { t.invalidate() } - } } } diff --git a/Sources/VPNBarApp/VPNManager.swift b/Sources/VPNBarApp/VPNManager.swift index 19145f0..7f794a9 100644 --- a/Sources/VPNBarApp/VPNManager.swift +++ b/Sources/VPNBarApp/VPNManager.swift @@ -123,6 +123,7 @@ class VPNManager: VPNManagerProtocol { updateTimer = nil if let token = statusObserverToken { NotificationCenter.default.removeObserver(token) + statusObserverToken = nil } }