Compare commits
2 Commits
main
...
feat/conne
Author | SHA1 | Date |
---|---|---|
![]() |
aefe583213 | |
![]() |
bf25b04f9d |
|
@ -10,7 +10,7 @@
|
|||
--elseposition same-line
|
||||
|
||||
--enable fileHeader
|
||||
--header "//\n// Copyright 2018-{created.year} Amazon.com,\n// Inc. or its affiliates. All Rights Reserved.\n//\n// SPDX-License-Identifier: Apache-2.0\n//"
|
||||
--header "//\n// Copyright 2018-2021 Amazon.com,\n// Inc. or its affiliates. All Rights Reserved.\n//\n// SPDX-License-Identifier: Apache-2.0\n//"
|
||||
|
||||
--disable hoistPatternLet
|
||||
--patternlet inline
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
217F39F12406EA4000F1A0B3 /* MockConnectionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39EB2406EA3F00F1A0B3 /* MockConnectionProvider.swift */; };
|
||||
217F39F22406EA4000F1A0B3 /* ConnectionProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39ED2406EA4000F1A0B3 /* ConnectionProviderTests.swift */; };
|
||||
217F39F32406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217F39EF2406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift */; };
|
||||
219BFF3B27A38902000FC148 /* schema.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 219BFF3A27A38902000FC148 /* schema.graphql */; };
|
||||
219BFF4927A3B238000FC148 /* ConnectivityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219BFF4827A3B237000FC148 /* ConnectivityMonitor.swift */; };
|
||||
219BFF4B27A3B24F000FC148 /* ConnectivityPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 219BFF4A27A3B24F000FC148 /* ConnectivityPath.swift */; };
|
||||
21D38B412409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D38B402409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift */; };
|
||||
21D38B432409AFBD00EC2A8D /* AppSyncRealTimeClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217F398F2405D9D500F1A0B3 /* AppSyncRealTimeClient.framework */; };
|
||||
21D38B4C2409B6C000EC2A8D /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = 21D38B4B2409B6C000EC2A8D /* amplifyconfiguration.json */; };
|
||||
|
@ -159,6 +162,9 @@
|
|||
217F39EB2406EA3F00F1A0B3 /* MockConnectionProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockConnectionProvider.swift; sourceTree = "<group>"; };
|
||||
217F39ED2406EA4000F1A0B3 /* ConnectionProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionProviderTests.swift; sourceTree = "<group>"; };
|
||||
217F39EF2406EA4000F1A0B3 /* RealtimeGatewayURLInterceptorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealtimeGatewayURLInterceptorTests.swift; sourceTree = "<group>"; };
|
||||
219BFF3A27A38902000FC148 /* schema.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = schema.graphql; sourceTree = "<group>"; };
|
||||
219BFF4827A3B237000FC148 /* ConnectivityMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityMonitor.swift; sourceTree = "<group>"; };
|
||||
219BFF4A27A3B24F000FC148 /* ConnectivityPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityPath.swift; sourceTree = "<group>"; };
|
||||
21D38B3E2409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppSyncRealTimeClientIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
21D38B402409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientIntegrationTests.swift; sourceTree = "<group>"; };
|
||||
21D38B422409AFBD00EC2A8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -292,9 +298,10 @@
|
|||
217F39912405D9D500F1A0B3 /* AppSyncRealTimeClient */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
217F39932405D9D500F1A0B3 /* Info.plist */,
|
||||
217F39B82406E98300F1A0B3 /* Connection */,
|
||||
217F39A92406E98300F1A0B3 /* ConnectionProvider */,
|
||||
219BFF4727A3B223000FC148 /* ConnectivityMonitor */,
|
||||
217F39932405D9D500F1A0B3 /* Info.plist */,
|
||||
21D38B6E240A272F00EC2A8D /* Interceptor */,
|
||||
217F39C62406E98400F1A0B3 /* Support */,
|
||||
217F39C12406E98400F1A0B3 /* Websocket */,
|
||||
|
@ -437,6 +444,15 @@
|
|||
path = Support;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
219BFF4727A3B223000FC148 /* ConnectivityMonitor */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
219BFF4827A3B237000FC148 /* ConnectivityMonitor.swift */,
|
||||
219BFF4A27A3B24F000FC148 /* ConnectivityPath.swift */,
|
||||
);
|
||||
path = ConnectivityMonitor;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
21D38B3F2409AFBD00EC2A8D /* AppSyncRealTimeClientIntegrationTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -497,6 +513,7 @@
|
|||
21D38B95240C4DC200EC2A8D /* Support */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
219BFF3A27A38902000FC148 /* schema.graphql */,
|
||||
21D38B98240C4E1C00EC2A8D /* ConfigurationHelper.swift */,
|
||||
21D38B96240C4DCF00EC2A8D /* Error+Extension.swift */,
|
||||
21D38B9C240C540D00EC2A8D /* TestCommonConstants.swift */,
|
||||
|
@ -753,6 +770,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
21D38B4E2409B8B200EC2A8D /* README.md in Resources */,
|
||||
219BFF3B27A38902000FC148 /* schema.graphql in Resources */,
|
||||
21D38B4C2409B6C000EC2A8D /* amplifyconfiguration.json in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1103,6 +1121,7 @@
|
|||
217F39CD2406E98400F1A0B3 /* InterceptableConnection.swift in Sources */,
|
||||
21D38B8E240A3C2300EC2A8D /* ConnectionProviderFactory.swift in Sources */,
|
||||
217F39E02406E98400F1A0B3 /* AppSyncWebsocketProvider.swift in Sources */,
|
||||
219BFF4927A3B238000FC148 /* ConnectivityMonitor.swift in Sources */,
|
||||
FAB7E91224D2644E00DF1EA1 /* RealtimeConnectionProvider+StaleConnection.swift in Sources */,
|
||||
217F39D32406E98400F1A0B3 /* RealtimeConnectionProvider.swift in Sources */,
|
||||
217F39D12406E98400F1A0B3 /* AppSyncConnectionRequest.swift in Sources */,
|
||||
|
@ -1126,6 +1145,7 @@
|
|||
217F39D42406E98400F1A0B3 /* RealtimeConnectionProvider+Websocket.swift in Sources */,
|
||||
217F39DC2406E98400F1A0B3 /* AppSyncSubscriptionConnection.swift in Sources */,
|
||||
21D38B6D240A262800EC2A8D /* AppSyncJSONHelper.swift in Sources */,
|
||||
219BFF4B27A3B24F000FC148 /* ConnectivityPath.swift in Sources */,
|
||||
217F39CE2406E98400F1A0B3 /* ConnectionProviderError.swift in Sources */,
|
||||
217F39E12406E98400F1A0B3 /* StarscreamAdapter.swift in Sources */,
|
||||
217F39D72406E98400F1A0B3 /* RealtimeConnectionProvider+ConnectionInterceptable.swift in Sources */,
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableThreadSanitizer = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
|
|
@ -33,6 +33,28 @@ extension RealtimeConnectionProvider {
|
|||
staleConnectionTimer?.resetCountdown()
|
||||
}
|
||||
|
||||
/// Handle updates from the ConnectivityMonitor
|
||||
func handleConnectivityUpdates(connectivity: ConnectivityPath) {
|
||||
connectionQueue.async {[weak self] in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
AppSyncLogger.debug("[RealtimeConnectionProvider] Status: \(self.status). Connectivity status: \(connectivity.status)")
|
||||
if self.status == .connected && connectivity.status == .unsatisfied && !self.isStaleConnection {
|
||||
AppSyncLogger.debug("[RealtimeConnectionProvider] Connetion is stale. Pending reconnect on connectivity.")
|
||||
self.isStaleConnection = true
|
||||
|
||||
} else if self.status == .connected && self.isStaleConnection && connectivity.status == .satisfied {
|
||||
AppSyncLogger.debug("[RealtimeConnectionProvider] Connetion is stale. Disconnecting to begin reconnect.")
|
||||
if self.staleConnectionTimer != nil {
|
||||
self.stopStaleConnectionTimer()
|
||||
}
|
||||
|
||||
self.disconnectStaleConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fired when the stale connection timer expires
|
||||
private func disconnectStaleConnection() {
|
||||
connectionQueue.async {[weak self] in
|
||||
|
@ -41,9 +63,9 @@ extension RealtimeConnectionProvider {
|
|||
}
|
||||
AppSyncLogger.error("[RealtimeConnectionProvider] Realtime connection is stale, disconnecting.")
|
||||
self.status = .notConnected
|
||||
self.isStaleConnection = false
|
||||
self.websocket.disconnect()
|
||||
self.updateCallback(event: .error(ConnectionProviderError.connection))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,12 +28,18 @@ public class RealtimeConnectionProvider: ConnectionProvider {
|
|||
/// alive" message will cause the timer to be reset to the full interval.
|
||||
var staleConnectionTimer: CountdownTimer?
|
||||
|
||||
/// Intermediate state when the connection is connected and connectivity updates to unsatisfied (offline)
|
||||
var isStaleConnection: Bool
|
||||
|
||||
/// Manages concurrency for socket connections, disconnections, writes, and status reports.
|
||||
///
|
||||
/// Each connection request will be sent to this queue. Connection request are
|
||||
/// handled one at a time.
|
||||
let connectionQueue: DispatchQueue
|
||||
|
||||
/// Monitor for connectivity updates to disconnect the current connection if it flips between connectivity statuses
|
||||
let connectivityMonitor: ConnectivityMonitor
|
||||
|
||||
/// The serial queue on which status & message callbacks from the web socket are invoked.
|
||||
private let serialCallbackQueue = DispatchQueue(
|
||||
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.callbackQueue"
|
||||
|
@ -51,6 +57,9 @@ public class RealtimeConnectionProvider: ConnectionProvider {
|
|||
self.connectionQueue = DispatchQueue(
|
||||
label: "com.amazonaws.AppSyncRealTimeConnectionProvider.serialQueue"
|
||||
)
|
||||
self.isStaleConnection = false
|
||||
self.connectivityMonitor = ConnectivityMonitor()
|
||||
connectivityMonitor.start(connectivityUpdates: handleConnectivityUpdates(connectivity:))
|
||||
}
|
||||
|
||||
// MARK: - ConnectionProvider methods
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// Copyright 2018-2021 Amazon.com,
|
||||
// Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
typealias ConnectivityUpdates = (ConnectivityPath) -> Void
|
||||
|
||||
protocol AnyConnectivityMonitor {
|
||||
func start(connectivityUpdatesQueue: DispatchQueue, onConnectivityUpdates: @escaping ConnectivityUpdates)
|
||||
func cancel()
|
||||
}
|
||||
|
||||
class ConnectivityMonitor {
|
||||
var connectivity: ConnectivityPath?
|
||||
|
||||
private let connectivityUpdatesQueue = DispatchQueue(
|
||||
label: "com.amazonaws.ConnectivityMonitor.connectivityUpdatesQueue",
|
||||
qos: .background
|
||||
)
|
||||
private var monitor: AnyConnectivityMonitor?
|
||||
|
||||
init(connectivityMonitor: AnyConnectivityMonitor? = nil) {
|
||||
self.monitor = connectivityMonitor
|
||||
}
|
||||
|
||||
func start(connectivityUpdates: @escaping ConnectivityUpdates) {
|
||||
if let monitor = monitor {
|
||||
monitor.start(
|
||||
connectivityUpdatesQueue: connectivityUpdatesQueue,
|
||||
onConnectivityUpdates: connectivityUpdates
|
||||
)
|
||||
} else if #available(iOS 12.0, *) {
|
||||
let monitor = NetworkMonitor()
|
||||
self.monitor = monitor
|
||||
monitor.start(
|
||||
connectivityUpdatesQueue: connectivityUpdatesQueue,
|
||||
onConnectivityUpdates: connectivityUpdates
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let monitor = monitor else {
|
||||
return
|
||||
}
|
||||
monitor.cancel()
|
||||
self.monitor = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *)
|
||||
class NetworkMonitor: AnyConnectivityMonitor {
|
||||
private var monitor: NWPathMonitor?
|
||||
private var onConnectivityUpdates: ConnectivityUpdates?
|
||||
private var connectivityUpdatesQueue: DispatchQueue?
|
||||
private let queue = DispatchQueue(label: "com.amazonaws.NetworkMonitor.queue", qos: .background)
|
||||
|
||||
func start(connectivityUpdatesQueue: DispatchQueue, onConnectivityUpdates: @escaping ConnectivityUpdates) {
|
||||
self.connectivityUpdatesQueue = connectivityUpdatesQueue
|
||||
self.onConnectivityUpdates = onConnectivityUpdates
|
||||
// A new instance is required each time a monitor is started
|
||||
let monitor = NWPathMonitor()
|
||||
monitor.pathUpdateHandler = didUpdate(path:)
|
||||
monitor.start(queue: queue)
|
||||
self.monitor = monitor
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
guard let monitor = monitor else { return }
|
||||
defer {
|
||||
self.monitor = nil
|
||||
}
|
||||
monitor.cancel()
|
||||
}
|
||||
|
||||
func didUpdate(path: NWPath) {
|
||||
guard let onConnectivityUpdates = onConnectivityUpdates,
|
||||
let connectivityUpdatesQueue = connectivityUpdatesQueue else {
|
||||
return
|
||||
}
|
||||
let connectivityPath = ConnectivityPath(path: path)
|
||||
connectivityUpdatesQueue.async {
|
||||
onConnectivityUpdates(connectivityPath)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// Copyright 2018-2021 Amazon.com,
|
||||
// Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
struct ConnectivityPath {
|
||||
let status: ConnectivityStatus
|
||||
let availableInterfaces: [ConnectivityInterface]
|
||||
let isExpensive: Bool
|
||||
let supportsDNS: Bool
|
||||
let supportsIPv4: Bool
|
||||
let supportsIPv6: Bool
|
||||
|
||||
init(
|
||||
status: ConnectivityStatus = .unsatisfied,
|
||||
availableInterfaces: [ConnectivityInterface] = [],
|
||||
isExpensive: Bool = false,
|
||||
supportsDNS: Bool = false,
|
||||
supportsIPv4: Bool = false,
|
||||
supportsIPv6: Bool = false
|
||||
) {
|
||||
self.status = status
|
||||
self.availableInterfaces = availableInterfaces
|
||||
self.isExpensive = isExpensive
|
||||
self.supportsDNS = supportsDNS
|
||||
self.supportsIPv4 = supportsIPv4
|
||||
self.supportsIPv6 = supportsIPv6
|
||||
}
|
||||
}
|
||||
|
||||
extension ConnectivityPath: CustomStringConvertible {
|
||||
var description: String {
|
||||
[
|
||||
"\(status): \(availableInterfaces.description)",
|
||||
"Expensive = \(isExpensive ? "YES" : "NO")",
|
||||
"DNS = \(supportsDNS ? "YES" : "NO")",
|
||||
"IPv4 = \(supportsIPv4 ? "YES" : "NO")",
|
||||
"IPv6 = \(supportsIPv6 ? "YES" : "NO")"
|
||||
].joined(separator: "; ")
|
||||
}
|
||||
}
|
||||
|
||||
extension ConnectivityPath {
|
||||
@available(iOS 12.0, *)
|
||||
init(path: NWPath) {
|
||||
self.status = ConnectivityStatus(status: path.status)
|
||||
self.availableInterfaces = path.availableInterfaces.map { ConnectivityInterface(interface: $0) }
|
||||
self.isExpensive = path.isExpensive
|
||||
self.supportsDNS = path.supportsDNS
|
||||
self.supportsIPv4 = path.supportsIPv4
|
||||
self.supportsIPv6 = path.supportsIPv6
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnectivityInterfaceType: String {
|
||||
case other
|
||||
case wifi
|
||||
case cellular
|
||||
case wiredEthernet
|
||||
case loopback
|
||||
}
|
||||
|
||||
extension ConnectivityInterfaceType {
|
||||
@available(iOS 12.0, *)
|
||||
init(interfaceType: NWInterface.InterfaceType) {
|
||||
switch interfaceType {
|
||||
case .other:
|
||||
self = .other
|
||||
case .wifi:
|
||||
self = .wifi
|
||||
case .cellular:
|
||||
self = .cellular
|
||||
case .wiredEthernet:
|
||||
self = .wiredEthernet
|
||||
case .loopback:
|
||||
self = .loopback
|
||||
@unknown default:
|
||||
self = .other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectivityInterface {
|
||||
public let name: String
|
||||
public let type: ConnectivityInterfaceType
|
||||
|
||||
public init(name: String, type: ConnectivityInterfaceType) {
|
||||
self.name = name
|
||||
self.type = type
|
||||
}
|
||||
}
|
||||
extension ConnectivityInterface {
|
||||
@available(iOS 12.0, *)
|
||||
init(interface: NWInterface) {
|
||||
self.name = interface.name
|
||||
self.type = ConnectivityInterfaceType(interfaceType: interface.type)
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnectivityStatus: String {
|
||||
case satisfied
|
||||
case unsatisfied
|
||||
case requiresConnection
|
||||
}
|
||||
|
||||
extension ConnectivityStatus {
|
||||
@available(iOS 12.0, *)
|
||||
init(status: NWPath.Status) {
|
||||
switch status {
|
||||
case .satisfied:
|
||||
self = .satisfied
|
||||
case .unsatisfied:
|
||||
self = .unsatisfied
|
||||
case .requiresConnection:
|
||||
self = .requiresConnection
|
||||
@unknown default:
|
||||
self = .unsatisfied
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue