iOS/Sources/Shared/Environment/ConnectivityWrapper.swift
Bruno Pantaleão Gonçalves 1da8113507
Sync network info before performing intents (#4147)
Added calls to syncNetworkInformation() at the start of various
perform() methods for AppIntents and widget controls to ensure
up-to-date connectivity data before executing actions. Also refactored
ConnectivityWrapper to support async network info synchronization.

<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-29 12:50:47 +00:00

146 lines
5.0 KiB
Swift

import Foundation
#if os(iOS)
import CoreTelephony
import Reachability
#endif
import Communicator
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
)
}
#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?()
#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()
}
}
}
}