Compare commits
4 Commits
main
...
fix/dup-re
Author | SHA1 | Date |
---|---|---|
![]() |
458422c806 | |
![]() |
3c31b9830c | |
![]() |
a9ed4bfc87 | |
![]() |
d6f79565ec |
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
0B59161BB37D32073E4FD61B /* Pods_AppSyncRTCSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CF486070B34EFD15B4DB8FC /* Pods_AppSyncRTCSample.framework */; };
|
||||
2143D46727B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2143D46627B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift */; };
|
||||
2164E65D2639AD5600385027 /* StarscreamAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2164E65C2639AD5600385027 /* StarscreamAdapterTests.swift */; };
|
||||
2164E674263C58CE00385027 /* AppSyncRealTimeClientTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2164E673263C58CD00385027 /* AppSyncRealTimeClientTestBase.swift */; };
|
||||
217F39992405D9D500F1A0B3 /* AppSyncRealTimeClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 217F398F2405D9D500F1A0B3 /* AppSyncRealTimeClient.framework */; };
|
||||
|
@ -121,6 +122,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
18D6E56CE03BAC33493CC19B /* Pods-HostApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HostApp.debug.xcconfig"; path = "Target Support Files/Pods-HostApp/Pods-HostApp.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
2143D46627B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealTimeConnectionProviderResponseTests.swift; sourceTree = "<group>"; };
|
||||
2164E65C2639AD5600385027 /* StarscreamAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarscreamAdapterTests.swift; sourceTree = "<group>"; };
|
||||
2164E673263C58CD00385027 /* AppSyncRealTimeClientTestBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncRealTimeClientTestBase.swift; sourceTree = "<group>"; };
|
||||
217F398F2405D9D500F1A0B3 /* AppSyncRealTimeClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppSyncRealTimeClient.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -263,6 +265,14 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
2143D46527B5D9730066B2F7 /* AppSyncRealTimeConnection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2143D46627B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift */,
|
||||
);
|
||||
path = AppSyncRealTimeConnection;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
217F39852405D9D500F1A0B3 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -421,6 +431,7 @@
|
|||
217F39EC2406EA4000F1A0B3 /* ConnectionProvider */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2143D46527B5D9730066B2F7 /* AppSyncRealTimeConnection */,
|
||||
FA67507F24D339B0005A1345 /* ConnectionProviderStaleConnectionTests.swift */,
|
||||
217F39ED2406EA4000F1A0B3 /* ConnectionProviderTests.swift */,
|
||||
FA67507C24D338FA005A1345 /* RealtimeConnectionProviderTestBase.swift */,
|
||||
|
@ -1139,6 +1150,7 @@
|
|||
files = (
|
||||
21D38B89240A39E400EC2A8D /* OIDCAuthInterceptorTests.swift in Sources */,
|
||||
217F39F22406EA4000F1A0B3 /* ConnectionProviderTests.swift in Sources */,
|
||||
2143D46727B5D9B40066B2F7 /* RealTimeConnectionProviderResponseTests.swift in Sources */,
|
||||
FA67507824D3244A005A1345 /* MockWebsocketProvider.swift in Sources */,
|
||||
217F39F02406EA4000F1A0B3 /* AppSyncSubscriptionConnectionTests.swift in Sources */,
|
||||
FA67507B24D338CB005A1345 /* Error+Extension.swift in Sources */,
|
||||
|
|
|
@ -18,7 +18,6 @@ extension AppSyncSubscriptionConnection {
|
|||
return
|
||||
}
|
||||
if connectionState == .connected {
|
||||
AppSyncLogger.debug("[AppSyncSubscriptionConnection] \(#function): connection is connected, start subscription.")
|
||||
startSubscription()
|
||||
}
|
||||
}
|
||||
|
@ -32,9 +31,10 @@ extension AppSyncSubscriptionConnection {
|
|||
else {
|
||||
return
|
||||
}
|
||||
|
||||
AppSyncLogger.debug("[AppSyncSubscriptionConnection] \(#function): connection is connected, start subscription for \(subscriptionItem.identifier) ")
|
||||
subscriptionState = .inProgress
|
||||
|
||||
throttled = false
|
||||
|
||||
guard let payload = convertToPayload(
|
||||
for: subscriptionItem.requestString,
|
||||
variables: subscriptionItem.variables
|
||||
|
|
|
@ -16,9 +16,6 @@ extension AppSyncSubscriptionConnection {
|
|||
}
|
||||
|
||||
guard response.id == subscriptionItem.identifier else {
|
||||
AppSyncLogger.verbose("""
|
||||
[AppSyncSubscriptionConnection] \(#function): \(subscriptionItem.identifier). Ignoring data event for \(response.id ?? "(null)")
|
||||
""")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -15,12 +15,52 @@ extension AppSyncSubscriptionConnection {
|
|||
return
|
||||
}
|
||||
|
||||
// If the error identifier is not for the this connection
|
||||
// If the error identifier is not for the this subscription
|
||||
// we return immediately without handling the error.
|
||||
if case let ConnectionProviderError.subscription(identifier, _) = error,
|
||||
identifier != subscriptionItem.identifier {
|
||||
return
|
||||
}
|
||||
|
||||
if case let ConnectionProviderError.limitExceeded(identifier) = error {
|
||||
// We do not know which subscription this is for, either ignore it or send the error back
|
||||
// Don't go pass this check since it's not retryable, return from here
|
||||
if identifier == nil {
|
||||
// if we are subscribed already, then ignore it.
|
||||
if subscriptionState == .subscribed {
|
||||
return
|
||||
} else {
|
||||
// If we are .inProgress or .notSubscribed, set it to `.notSubscribed`, send the error back.
|
||||
subscriptionState = .notSubscribed
|
||||
if !throttled {
|
||||
// TODO: we sent this back for this subscription, but we still don't really know which
|
||||
// subscription was the one that was throttled.
|
||||
subscriptionItem.subscriptionEventHandler(.failed(error), subscriptionItem)
|
||||
}
|
||||
|
||||
// Once we've sent it once, circuit break here using some state on the subscription.
|
||||
throttled = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If there is an identifier, and does not equal this subscription's identifier, ignore the error.
|
||||
if identifier != subscriptionItem.identifier {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if case ConnectionProviderError.other = error {
|
||||
AppSyncLogger.warn("[AppSyncSubscriptionConnection] \(#function): other error \(subscriptionItem.identifier)")
|
||||
// Again this is only ever returned if there is no `id` and it is not LimitExceeded
|
||||
// the default case should also be throttled at the subscription as we don't know whether it
|
||||
// happened because of this subscription
|
||||
// of because of something else.
|
||||
subscriptionItem.subscriptionEventHandler(.failed(error), subscriptionItem)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
AppSyncSubscriptionConnection.logExtendedErrorInfo(for: error)
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ public class AppSyncSubscriptionConnection: SubscriptionConnection, RetryableCon
|
|||
|
||||
/// Retry logic to handle
|
||||
var retryHandler: ConnectionRetryHandler?
|
||||
|
||||
var throttled: Bool = false
|
||||
|
||||
public init(provider: ConnectionProvider) {
|
||||
self.connectionProvider = provider
|
||||
|
|
|
@ -111,6 +111,13 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
|
|||
return
|
||||
}
|
||||
|
||||
if response.isRateLimitExceededError() {
|
||||
AppSyncLogger.debug("[RealtimeConnectionProvider] isRateLimitExceededError")
|
||||
let limitExceedError = ConnectionProviderError.limitExceeded(nil)
|
||||
updateCallback(event: .error(limitExceedError))
|
||||
return
|
||||
}
|
||||
|
||||
// Return back as generic error if there is no identifier.
|
||||
guard let identifier = response.id else {
|
||||
let genericError = ConnectionProviderError.other
|
||||
|
@ -118,9 +125,7 @@ extension RealtimeConnectionProvider: AppSyncWebsocketDelegate {
|
|||
return
|
||||
}
|
||||
|
||||
// Map to limit exceed error if we get MaxSubscriptionsReachedException
|
||||
if let errorType = response.payload?["errorType"],
|
||||
errorType == "MaxSubscriptionsReachedException" {
|
||||
if response.isMaxSubscriptionReachedError() {
|
||||
let limitExceedError = ConnectionProviderError.limitExceeded(identifier)
|
||||
updateCallback(event: .error(limitExceedError))
|
||||
return
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// More information about the response can be found here
|
||||
/// https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#connection-init-message
|
||||
struct RealtimeConnectionProviderResponse {
|
||||
|
||||
/// Subscription Identifier
|
||||
|
@ -52,3 +54,67 @@ extension RealtimeConnectionProviderResponse: Decodable {
|
|||
case responseType = "type"
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper methods to check which type of errors, such as `MaxSubscriptionsReachedError`, `LimitExceededError`.
|
||||
/// Errors have the following shape
|
||||
///
|
||||
/// "type": "error": A constant <string> parameter.
|
||||
/// "id": <string>: The ID of the corresponding registered subscription, if relevant.
|
||||
/// "payload" <Object>: An object that contains the corresponding error information.
|
||||
///
|
||||
/// More information can be found here
|
||||
/// https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#error-message
|
||||
extension RealtimeConnectionProviderResponse {
|
||||
|
||||
func isMaxSubscriptionReachedError() -> Bool {
|
||||
// It is expected to contain payload with corresponding error information
|
||||
guard let payload = payload else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Keep this here for backwards compatibility for previously provisioned AppSync services
|
||||
if let errorType = payload["errorType"],
|
||||
errorType == "MaxSubscriptionsReachedException" {
|
||||
return true
|
||||
}
|
||||
|
||||
// The observed response from the service
|
||||
// { "id":"DB23EC80-C51A-4FEE-82F7-AA4949B4F299",
|
||||
// "type":"error",
|
||||
// "payload": {
|
||||
// "errors": {
|
||||
// "errorType":"MaxSubscriptionsReachedError",
|
||||
// "message":"Max number of 100 subscriptions reached" }}}
|
||||
if let errors = payload["errors"],
|
||||
case let .object(errorsObject) = errors,
|
||||
let errorType = errorsObject["errorType"],
|
||||
errorType == "MaxSubscriptionsReachedError" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isRateLimitExceededError() -> Bool {
|
||||
// It is expected to contain payload with corresponding error information
|
||||
guard let payload = payload else {
|
||||
return false
|
||||
}
|
||||
|
||||
// The observed response from the service
|
||||
// { "type":"error",
|
||||
// "payload": {
|
||||
// "errors": {
|
||||
// "errorType":"LimitExceededError",
|
||||
// "message":"Rate limit exceeded" }}}
|
||||
if let errors = payload["errors"],
|
||||
case let .object(errorsObject) = errors,
|
||||
let errorType = errorsObject["errorType"],
|
||||
errorType == "LimitExceededError" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -218,6 +218,10 @@ class AppSyncRealTimeClientIntegrationTests: AppSyncRealTimeClientTestBase {
|
|||
wait(for: [expectedPerforms], timeout: 1)
|
||||
assertStatus(of: realTimeConnectionProvider, equals: .notConnected)
|
||||
}
|
||||
|
||||
func testMaxSubscriptionConnection() {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Copyright Amazon.com Inc. or its affiliates.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import AppSyncRealTimeClient
|
||||
|
||||
class RealTimeConnectionProviderResponseTests: XCTestCase {
|
||||
|
||||
func testIsMaxSubscriptionReached_EmptyPayload() throws {
|
||||
let response = RealtimeConnectionProviderResponse(
|
||||
id: "id",
|
||||
payload: nil,
|
||||
type: .error
|
||||
)
|
||||
|
||||
XCTAssertFalse(response.isMaxSubscriptionReachedError())
|
||||
}
|
||||
|
||||
func testIsMaxSubscriptionReached_MaxSubscriptionsReachedException() throws {
|
||||
let payload = ["errorType": AppSyncJSONValue.string("MaxSubscriptionsReachedException")]
|
||||
let response = RealtimeConnectionProviderResponse(
|
||||
id: "id",
|
||||
payload: payload,
|
||||
type: .error
|
||||
)
|
||||
|
||||
XCTAssertTrue(response.isMaxSubscriptionReachedError())
|
||||
}
|
||||
|
||||
func testIsMaxSubscriptionReached_MaxSubscriptionsReachedError() throws {
|
||||
let payload = ["errors": AppSyncJSONValue.object(["errorType": "MaxSubscriptionsReachedError"])]
|
||||
let response = RealtimeConnectionProviderResponse(
|
||||
id: "id",
|
||||
payload: payload,
|
||||
type: .error
|
||||
)
|
||||
|
||||
XCTAssertTrue(response.isMaxSubscriptionReachedError())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue