Files
iOS/Sources/Shared/Environment/ConnectivityWrapper.swift
Bruno Pantaleão 0008727ff3 WIP
2026-06-15 09:36:28 +02:00

163 lines
5.7 KiB
Swift

import Foundation
#if os(iOS)
import CoreTelephony
import Reachability
#endif
#if canImport(Communicator)
import Communicator
#endif
import NetworkExtension
/// Wrapper around CoreTelephony, Reachability
public class ConnectivityWrapper {
public var connectivityDidChangeNotification: () -> Notification.Name
public var hasWiFi: () -> Bool
public var currentWiFiSSID: () -> String?
public var currentWiFiBSSID: () -> String?
public var currentNetworkHardwareAddress: () -> String?
public var simpleNetworkType: () -> NetworkType
public var cellularNetworkType: () -> NetworkType
public var networkAttributes: () -> [String: Any]
#if targetEnvironment(macCatalyst)
init() {
self.hasWiFi = { Current.macBridge.networkConnectivity.hasWiFi }
self.currentWiFiSSID = { Current.macBridge.networkConnectivity.wifi?.ssid }
self.currentWiFiBSSID = { Current.macBridge.networkConnectivity.wifi?.bssid }
self.connectivityDidChangeNotification = { Current.macBridge.networkConnectivityDidChangeNotification }
self.simpleNetworkType = {
switch Current.macBridge.networkConnectivity.networkType {
case .ethernet: return .ethernet
case .wifi: return .wifi
case .unknown: return .unknown
case .noNetwork: return .noConnection
}
}
self.currentNetworkHardwareAddress = { Current.macBridge.networkConnectivity.interface?.hardwareAddress }
self.cellularNetworkType = { .unknown }
self.networkAttributes = {
if let interface = Current.macBridge.networkConnectivity.interface {
return [
"Name": interface.name,
"Hardware Address": interface.hardwareAddress,
]
} else {
return [:]
}
}
}
#elseif os(iOS)
init() {
let reachability = try? Reachability()
do {
try reachability?.startNotifier()
} catch {
Current.Log.error("failed to start reachability notifier: \(error)")
}
self.hasWiFi = { true }
self.currentWiFiSSID = {
nil
}
self.currentWiFiBSSID = {
nil
}
self.connectivityDidChangeNotification = { .reachabilityChanged }
self.simpleNetworkType = { reachability?.getSimpleNetworkType() ?? .unknown }
self.cellularNetworkType = { reachability?.getNetworkType() ?? .unknown }
self.currentNetworkHardwareAddress = { nil }
self.networkAttributes = { [:] }
syncNetworkInformation()
NotificationCenter.default.addObserver(
self,
selector: #selector(connectivityDidChange(_:)),
name: .reachabilityChanged,
object: nil
)
}
#elseif os(macOS)
init() {
// TODO(mac-port): retrieve real network information (e.g. CoreWLAN/SystemConfiguration)
self.hasWiFi = { true }
self.currentWiFiSSID = { nil }
self.currentWiFiBSSID = { nil }
self.connectivityDidChangeNotification = { .init(rawValue: "_noop_") }
self.simpleNetworkType = { .unknown }
self.cellularNetworkType = { .unknown }
self.currentNetworkHardwareAddress = { nil }
self.networkAttributes = { [:] }
}
#else
init() {
self.hasWiFi = { true }
self.currentWiFiSSID = {
let ssid = WatchUserDefaults.shared.string(for: .watchSSID)
Current.Log.verbose("Watch current WiFi SSID: \(String(describing: ssid))")
return ssid
}
self.currentWiFiBSSID = { nil }
self.connectivityDidChangeNotification = { .init(rawValue: "_noop_") }
self.simpleNetworkType = { .unknown }
self.cellularNetworkType = { .unknown }
self.currentNetworkHardwareAddress = { nil }
self.networkAttributes = { [:] }
syncNetworkInformation()
// Reachability observer is not available for watchOS
}
#endif
#if os(iOS) && !targetEnvironment(macCatalyst)
public var telephonyCarriers: () -> [String: CTCarrier]? = {
CTTelephonyNetworkInfo().serviceSubscriberCellularProviders
}
public var telephonyRadioAccessTechnology: () -> [String: String]? = {
CTTelephonyNetworkInfo().serviceCurrentRadioAccessTechnology
}
#endif
@objc private func connectivityDidChange(_ note: Notification) {
syncNetworkInformation()
}
// TODO: Refactor SSID retrieval to be async instead of hacking around with completion handlers
public func syncNetworkInformation(completion: (() -> Void)? = nil) {
#if targetEnvironment(macCatalyst)
// macOS uses macBridge to retrieve network information
completion?()
#elseif os(macOS)
// TODO(mac-port): NEHotspotNetwork is unavailable on macOS; retrieve SSID/BSSID natively
completion?()
#else
NEHotspotNetwork.fetchCurrent { hotspotNetwork in
Current.Log
.verbose(
"Current SSID: \(String(describing: hotspotNetwork?.ssid)), current BSSID: \(String(describing: hotspotNetwork?.bssid))"
)
let ssid = hotspotNetwork?.ssid
self.currentWiFiSSID = {
#if targetEnvironment(simulator)
return "Simulator"
#endif
return ssid
}
let bssid = hotspotNetwork?.bssid
self.currentWiFiBSSID = { bssid }
completion?()
}
#endif
}
public func syncNetworkInformation() async {
await withCheckedContinuation { continuation in
syncNetworkInformation {
continuation.resume()
}
}
}
}