From ba2ee80fb9fa9e5342168641633ed19e85834840 Mon Sep 17 00:00:00 2001 From: Devin Binnie Date: Wed, 26 Aug 2026 12:37:39 -0400 Subject: [PATCH 1/3] [MM-70388] Fix SSID collection under VPN and after location authorization --- .../SessionAttributesCollector.kt | 5 ++- .../SessionAttributesCollector.swift | 44 +++++++++++++++---- ...-network-client-session-attributes.podspec | 4 +- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt b/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt index 86e22a174..35c09d710 100644 --- a/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt +++ b/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt @@ -90,9 +90,10 @@ class SessionAttributesCollector( val capabilities = activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) } val vpnActive = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true + val wifiActive = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true val interfaceType = when { vpnActive -> "vpn" - capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> "wifi" + wifiActive -> "wifi" capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> "cellular" capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) == true -> "ethernet" activeNetwork == null -> "" @@ -100,7 +101,7 @@ class SessionAttributesCollector( } val ipAddress = resolveIpAddress() - val ssid = if (interfaceType == "wifi") resolveSsid() else "" + val ssid = if (wifiActive) resolveSsid() else "" return NetworkSnapshot(interfaceType, ipAddress, vpnActive, ssid) } diff --git a/ios/SessionAttributes/SessionAttributesCollector.swift b/ios/SessionAttributes/SessionAttributesCollector.swift index 33fcc08a9..2418783e5 100644 --- a/ios/SessionAttributes/SessionAttributesCollector.swift +++ b/ios/SessionAttributes/SessionAttributesCollector.swift @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import CoreLocation import Foundation import Network import NetworkExtension @@ -13,7 +14,7 @@ struct NetworkSnapshot { let ssid: String } -public class SessionAttributesCollector { +public class SessionAttributesCollector: NSObject { public static let shared = SessionAttributesCollector() private let store = SessionAttributesStore.shared @@ -23,11 +24,14 @@ public class SessionAttributesCollector { private let pathMonitor = NWPathMonitor() private var cachedInterfaceType = "" private var cachedSsid = "" - private let ssidUnavailable: Bool + private var wifiAvailable = false + private var locationManager: CLLocationManager? - private init() { - ssidUnavailable = Bundle.main.bundlePath.hasSuffix(".appex") && - Bundle.main.bundleIdentifier?.contains("NotificationService") == true + private static let ssidUnavailable = + Bundle.main.bundleIdentifier?.contains("NotificationService") == true + + private override init() { + super.init() pathMonitor.pathUpdateHandler = { [weak self] path in guard let self else { @@ -45,13 +49,22 @@ public class SessionAttributesCollector { self.cachedInterfaceType = "other" } - if self.cachedInterfaceType == "wifi" { + self.wifiAvailable = path.availableInterfaces.contains { $0.type == .wifi } + if self.wifiAvailable { self.refreshSsid() } else { self.cachedSsid = "" } } pathMonitor.start(queue: monitorQueue) + + if !Self.ssidUnavailable { + DispatchQueue.main.async { + let manager = CLLocationManager() + manager.delegate = self + self.locationManager = manager + } + } } func collect(_ name: String, serverUrl: String) -> String { @@ -119,18 +132,17 @@ public class SessionAttributesCollector { let ipAddress = resolveIpAddress() return monitorQueue.sync { let interfaceType = vpnActive ? "vpn" : cachedInterfaceType - let ssid = interfaceType == "wifi" ? cachedSsid : "" return NetworkSnapshot( interfaceType: interfaceType, ipAddress: ipAddress, vpnActive: vpnActive, - ssid: ssid + ssid: cachedSsid ) } } private func refreshSsid() { - guard !ssidUnavailable else { + guard !Self.ssidUnavailable else { cachedSsid = "" return } @@ -149,6 +161,9 @@ public class SessionAttributesCollector { ssid = "" } self.monitorQueue.async { + guard self.wifiAvailable else { + return + } self.cachedSsid = ssid } } @@ -225,3 +240,14 @@ public class SessionAttributesCollector { return address } } + +extension SessionAttributesCollector: CLLocationManagerDelegate { + public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + monitorQueue.async { + guard self.wifiAvailable else { + return + } + self.refreshSsid() + } + } +} diff --git a/react-native-network-client-session-attributes.podspec b/react-native-network-client-session-attributes.podspec index 801b07b6e..58c9d22bc 100644 --- a/react-native-network-client-session-attributes.podspec +++ b/react-native-network-client-session-attributes.podspec @@ -18,8 +18,8 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json"))) # the collision cannot be fixed in place. # # The sources here depend only on system frameworks (Foundation, Network, -# NetworkExtension, SystemConfiguration, CryptoKit), which is what makes them -# safe to link into app extensions. +# NetworkExtension, SystemConfiguration, CryptoKit, CoreLocation), which is what +# makes them safe to link into app extensions. # # Swift module name is react_native_network_client_session_attributes (CocoaPods # replaces the hyphens in the pod name with underscores). From 1242f154545d63b2e7c16de4867cb6023b68c227 Mon Sep 17 00:00:00 2001 From: Devin Binnie Date: Wed, 26 Aug 2026 12:52:27 -0400 Subject: [PATCH 2/3] PR feedback --- .../sessionattributes/SessionAttributesCollector.kt | 4 +++- ios/SessionAttributes/SessionAttributesCollector.swift | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt b/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt index 35c09d710..f84e58e97 100644 --- a/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt +++ b/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt @@ -90,7 +90,9 @@ class SessionAttributesCollector( val capabilities = activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) } val vpnActive = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true - val wifiActive = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + val wifiActive = connectivityManager.allNetworks.any { network -> + connectivityManager.getNetworkCapabilities(network)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true + } val interfaceType = when { vpnActive -> "vpn" wifiActive -> "wifi" diff --git a/ios/SessionAttributes/SessionAttributesCollector.swift b/ios/SessionAttributes/SessionAttributesCollector.swift index 2418783e5..b32bc8247 100644 --- a/ios/SessionAttributes/SessionAttributesCollector.swift +++ b/ios/SessionAttributes/SessionAttributesCollector.swift @@ -27,8 +27,8 @@ public class SessionAttributesCollector: NSObject { private var wifiAvailable = false private var locationManager: CLLocationManager? - private static let ssidUnavailable = - Bundle.main.bundleIdentifier?.contains("NotificationService") == true + private static let ssidUnavailable = Bundle.main.bundleURL.pathExtension == "appex" && + Bundle.main.bundleIdentifier?.contains("NotificationService") ?? false private override init() { super.init() From 2d04866d8dd9597758b53fa4ae7d46a55c2506a7 Mon Sep 17 00:00:00 2001 From: Devin Binnie Date: Wed, 26 Aug 2026 15:18:02 -0400 Subject: [PATCH 3/3] Fix Android VPN issue --- .../sessionattributes/SessionAttributesCollector.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt b/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt index f84e58e97..220bfa100 100644 --- a/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt +++ b/android/src/main/java/com/mattermost/networkclient/sessionattributes/SessionAttributesCollector.kt @@ -90,12 +90,9 @@ class SessionAttributesCollector( val capabilities = activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) } val vpnActive = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true - val wifiActive = connectivityManager.allNetworks.any { network -> - connectivityManager.getNetworkCapabilities(network)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true - } val interfaceType = when { vpnActive -> "vpn" - wifiActive -> "wifi" + capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> "wifi" capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> "cellular" capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) == true -> "ethernet" activeNetwork == null -> "" @@ -103,9 +100,8 @@ class SessionAttributesCollector( } val ipAddress = resolveIpAddress() - val ssid = if (wifiActive) resolveSsid() else "" - return NetworkSnapshot(interfaceType, ipAddress, vpnActive, ssid) + return NetworkSnapshot(interfaceType, ipAddress, vpnActive, resolveSsid()) } private fun resolveIpAddress(): String {