197 lines
5.4 KiB
Swift
197 lines
5.4 KiB
Swift
//
|
|
// Copyright 2018 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import Reachability
|
|
|
|
@objc(SSKReachabilityType)
|
|
public enum ReachabilityType: Int {
|
|
case any, wifi, cellular
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
public class SSKReachability: NSObject {
|
|
// Unlike reachabilityChanged, this notification is only fired:
|
|
//
|
|
// * If the app is ready.
|
|
// * If the app is not in the background.
|
|
@objc
|
|
public static let owsReachabilityDidChange = Notification.Name("owsReachabilityDidChange")
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
public protocol SSKReachabilityManager {
|
|
|
|
var isReachable: Bool { get }
|
|
|
|
func isReachable(via reachabilityType: ReachabilityType) -> Bool
|
|
}
|
|
|
|
public extension SSKReachabilityManager {
|
|
func isReachable(with configuration: NetworkInterfaceSet) -> Bool {
|
|
NetworkInterface.allCases.contains { interface in
|
|
configuration.isSuperset(of: interface.singleItemSet) && isReachable(via: interface.reachabilityType)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
public class SSKReachabilityManagerImpl: NSObject, SSKReachabilityManager {
|
|
|
|
private let backgroundSession = OWSURLSession(
|
|
securityPolicy: OWSURLSession.signalServiceSecurityPolicy,
|
|
configuration: .background(withIdentifier: "SSKReachabilityManagerImpl"),
|
|
canUseSignalProxy: false
|
|
)
|
|
|
|
// This property should only be accessed on the main thread.
|
|
private let reachability: Reachability
|
|
|
|
private struct Token {
|
|
let isReachable: Bool
|
|
let isReachableViaWiFi: Bool
|
|
let isReachableViaWWAN: Bool
|
|
|
|
static var empty: Token {
|
|
Token(isReachable: false, isReachableViaWiFi: false, isReachableViaWWAN: false)
|
|
}
|
|
}
|
|
private let token = AtomicValue<Token>(.empty, lock: .sharedGlobal)
|
|
|
|
// This property can be safely accessed from any thread.
|
|
public var isReachable: Bool {
|
|
isReachable(via: .any)
|
|
}
|
|
|
|
// This method can be safely called from any thread.
|
|
public func isReachable(via reachabilityType: ReachabilityType) -> Bool {
|
|
switch reachabilityType {
|
|
case .any:
|
|
return token.get().isReachable
|
|
case .wifi:
|
|
return token.get().isReachableViaWiFi
|
|
case .cellular:
|
|
return token.get().isReachableViaWWAN
|
|
}
|
|
}
|
|
|
|
private let appReadiness: AppReadiness
|
|
|
|
public init(appReadiness: AppReadiness) {
|
|
self.appReadiness = appReadiness
|
|
AssertIsOnMainThread()
|
|
|
|
self.reachability = Reachability.forInternetConnection()
|
|
|
|
super.init()
|
|
|
|
appReadiness.runNowOrWhenAppDidBecomeReadySync {
|
|
self.configure()
|
|
}
|
|
}
|
|
|
|
private func updateToken() {
|
|
AssertIsOnMainThread()
|
|
|
|
token.set(Token(isReachable: reachability.isReachable(),
|
|
isReachableViaWiFi: reachability.isReachableViaWiFi(),
|
|
isReachableViaWWAN: reachability.isReachableViaWWAN()))
|
|
}
|
|
|
|
private func configure() {
|
|
AssertIsOnMainThread()
|
|
|
|
NotificationCenter.default.addObserver(self,
|
|
selector: #selector(reachabilityChanged),
|
|
name: .reachabilityChanged,
|
|
object: nil)
|
|
|
|
startNotifier()
|
|
}
|
|
|
|
@objc
|
|
func reachabilityChanged() {
|
|
AssertIsOnMainThread()
|
|
|
|
guard appReadiness.isAppReady else {
|
|
owsFailDebug("App is unexpectedly not ready.")
|
|
return
|
|
}
|
|
|
|
updateToken()
|
|
|
|
NotificationCenter.default.post(name: SSKReachability.owsReachabilityDidChange, object: self)
|
|
|
|
scheduleWakeupRequestIfNecessary()
|
|
}
|
|
|
|
private func startNotifier() {
|
|
AssertIsOnMainThread()
|
|
|
|
guard appReadiness.isAppReady else {
|
|
owsFailDebug("App is unexpectedly not ready.")
|
|
return
|
|
}
|
|
guard reachability.startNotifier() else {
|
|
owsFailDebug("failed to start notifier")
|
|
return
|
|
}
|
|
|
|
updateToken()
|
|
|
|
scheduleWakeupRequestIfNecessary()
|
|
}
|
|
|
|
private func scheduleWakeupRequestIfNecessary() {
|
|
AssertIsOnMainThread()
|
|
|
|
// Start a background session to wake the app when the network
|
|
// becomes available. We start this immediately when we lose
|
|
// connectivity rather than waiting until the app is backgrounded,
|
|
// because if started while backgrounded when the app is woken up
|
|
// will be at the OSes discretion.
|
|
guard !isReachable else { return }
|
|
|
|
Logger.info("Scheduling wakeup request for pending message sends.")
|
|
|
|
Task { [backgroundSession] in
|
|
do {
|
|
_ = try await backgroundSession.performDownload(TSConstants.mainServiceIdentifiedURL, method: .get)
|
|
Logger.info("Finished wakeup request.")
|
|
} catch {
|
|
Logger.warn("Failed wakeup request \(error)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private extension NetworkInterface {
|
|
var reachabilityType: ReachabilityType {
|
|
switch self {
|
|
case .cellular: return .cellular
|
|
case .wifi: return .wifi
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
#if TESTABLE_BUILD
|
|
|
|
@objc
|
|
public class MockSSKReachabilityManager: NSObject, SSKReachabilityManager {
|
|
public var isReachable: Bool = false
|
|
public func isReachable(via reachabilityType: ReachabilityType) -> Bool { isReachable }
|
|
}
|
|
|
|
#endif
|