diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
index 6970be959c..c13ed00ec0 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
@@ -741,6 +741,26 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
AnyPublisher
+ func unsubscribe(id: String) async throws
+}
/**
The AppSyncRealTimeClient conforms to the AppSync real-time WebSocket protocol.
@@ -205,7 +213,7 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol {
- Parameters:
- id: unique identifier of the subscription.
*/
- func unsubscribe(id: String) async throws {
+ public func unsubscribe(id: String) async throws {
defer {
log.debug("[AppSyncRealTimeClient] deleted subscription with id: \(id)")
subscriptions.removeValue(forKey: id)
@@ -287,7 +295,7 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol {
.map { response -> AppSyncSubscriptionEvent? in
switch response.type {
case .connectionError, .error:
- return .error(Self.decodeAppSyncRealTimeResponseError(response.payload))
+ return response.payload.map { .error($0) }
case .data:
return response.payload.map { .data($0) }
default:
@@ -298,38 +306,6 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol {
.eraseToAnyPublisher()
}
- private static func decodeAppSyncRealTimeResponseError(_ data: JSONValue?) -> [Error] {
- let knownAppSyncRealTimeRequestErorrs =
- Self.decodeAppSyncRealTimeRequestError(data)
- .filter { !$0.isUnknown }
- if knownAppSyncRealTimeRequestErorrs.isEmpty {
- let graphQLErrors = Self.decodeGraphQLErrors(data)
- return graphQLErrors.isEmpty
- ? [APIError.operationError("Failed to decode AppSync error response", "", nil)]
- : graphQLErrors
- } else {
- return knownAppSyncRealTimeRequestErorrs
- }
- }
-
- private static func decodeGraphQLErrors(_ data: JSONValue?) -> [GraphQLError] {
- do {
- return try GraphQLErrorDecoder.decodeAppSyncErrors(data)
- } catch {
- log.debug("[AppSyncRealTimeClient] Failed to decode errors: \(error)")
- return []
- }
- }
-
- private static func decodeAppSyncRealTimeRequestError(_ data: JSONValue?) -> [AppSyncRealTimeRequest.Error] {
- guard let errorsJson = data?.errors else {
- log.error("[AppSyncRealTimeClient] No 'errors' field found in response json")
- return []
- }
- let errors = errorsJson.asArray ?? [errorsJson]
- return errors.compactMap(AppSyncRealTimeRequest.parseResponseError(error:))
- }
-
private func bindCancellableToConnection(_ cancellable: AnyCancellable) {
cancellable.store(in: &cancellablesBindToConnection)
}
@@ -438,15 +414,15 @@ extension Publisher where Output == AppSyncRealTimeSubscription.State, Failure =
}
extension AppSyncRealTimeClient: DefaultLogger {
- static var log: Logger {
+ public static var log: Logger {
Amplify.Logging.logger(forCategory: CategoryType.api.displayName, forNamespace: String(describing: self))
}
- nonisolated var log: Logger { Self.log }
+ public nonisolated var log: Logger { Self.log }
}
extension AppSyncRealTimeClient: Resettable {
- func reset() async {
+ public func reset() async {
subject.send(completion: .finished)
cancellables = Set()
cancellablesBindToConnection = Set()
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequest.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequest.swift
index 19599820b4..10fb87013a 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequest.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequest.swift
@@ -7,18 +7,23 @@
import Foundation
-import Combine
import Amplify
-public enum AppSyncRealTimeRequest {
+enum AppSyncRealTimeRequest {
case connectionInit
case start(StartRequest)
case stop(String)
- public struct StartRequest {
+ struct StartRequest {
let id: String
let data: String
let auth: AppSyncRealTimeRequestAuth?
+
+ init(id: String, data: String, auth: AppSyncRealTimeRequestAuth?) {
+ self.id = id
+ self.data = data
+ self.auth = auth
+ }
}
var id: String? {
@@ -78,7 +83,7 @@ extension AppSyncRealTimeRequest {
case unauthorized
case unknown(message: String? = nil, causedBy: Swift.Error? = nil, payload: [String: Any]?)
- var isUnknown: Bool {
+ public var isUnknown: Bool {
if case .unknown = self {
return true
}
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequestAuth.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequestAuth.swift
index 87e01b1842..fffaf97faf 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequestAuth.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeRequestAuth.swift
@@ -8,30 +8,48 @@
import Foundation
-public enum AppSyncRealTimeRequestAuth {
+enum AppSyncRealTimeRequestAuth {
case authToken(AuthToken)
case apiKey(ApiKey)
case iam(IAM)
- public struct AuthToken {
+ struct AuthToken {
let host: String
let authToken: String
+
+ init(host: String, authToken: String) {
+ self.host = host
+ self.authToken = authToken
+ }
}
- public struct ApiKey {
+ struct ApiKey {
let host: String
let apiKey: String
let amzDate: String
+
+ init(host: String, apiKey: String, amzDate: String) {
+ self.host = host
+ self.apiKey = apiKey
+ self.amzDate = amzDate
+ }
}
- public struct IAM {
+ struct IAM {
let host: String
let authToken: String
let securityToken: String
let amzDate: String
+
+ init(host: String, authToken: String, securityToken: String, amzDate: String) {
+ self.host = host
+ self.authToken = authToken
+ self.securityToken = securityToken
+ self.amzDate = amzDate
+ }
}
- public struct URLQuery {
+ struct URLQuery {
let header: AppSyncRealTimeRequestAuth
let payload: String
@@ -53,7 +71,7 @@ public enum AppSyncRealTimeRequestAuth {
urlComponents.queryItems = [
URLQueryItem(name: "header", value: headerJsonData.base64EncodedString()),
- URLQueryItem(name: "payload", value: try? payload.base64EncodedString())
+ URLQueryItem(name: "payload", value: payload.data(using: .utf8)?.base64EncodedString())
]
return urlComponents.url ?? url
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeResponse.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeResponse.swift
index dfec371035..36813b4bbb 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeResponse.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeResponse.swift
@@ -8,13 +8,13 @@
import Foundation
import Amplify
-public struct AppSyncRealTimeResponse {
+struct AppSyncRealTimeResponse {
- public let id: String?
- public let payload: JSONValue?
- public let type: EventType
+ let id: String?
+ let payload: JSONValue?
+ let type: EventType
- public enum EventType: String, Codable {
+ enum EventType: String, Codable {
case connectionAck = "connection_ack"
case startAck = "start_ack"
case stopAck = "complete"
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeSubscription.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeSubscription.swift
index d7e4c6ef42..2997311b7b 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeSubscription.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeSubscription.swift
@@ -9,7 +9,7 @@
import Foundation
import Combine
import Amplify
-@_spi(WebSocket) import AWSPluginsCore
+@_spi(RetryWithJitter) import InternalAmplifyNetwork
/**
AppSyncRealTimeSubscription reprensents one realtime subscription to AppSync realtime server.
@@ -36,8 +36,8 @@ actor AppSyncRealTimeSubscription {
private weak var appSyncRealTimeClient: AppSyncRealTimeClient?
- public let id: String
- public let query: String
+ let id: String
+ let query: String
init(id: String, query: String, appSyncRealTimeClient: AppSyncRealTimeClient) {
@@ -121,9 +121,9 @@ actor AppSyncRealTimeSubscription {
}
extension AppSyncRealTimeSubscription: DefaultLogger {
- static var log: Logger {
+ public static var log: Logger {
Amplify.Logging.logger(forCategory: CategoryType.api.displayName, forNamespace: String(describing: self))
}
- nonisolated var log: Logger { Self.log }
+ nonisolated public var log: Logger { Self.log }
}
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncSubscriptionEvent.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncSubscriptionEvent.swift
index ec86c53e6a..fbc0d27671 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncSubscriptionEvent.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncSubscriptionEvent.swift
@@ -9,10 +9,10 @@
import Foundation
import Amplify
-public enum AppSyncSubscriptionEvent {
+enum AppSyncSubscriptionEvent {
case subscribing
case subscribed
case data(JSONValue)
case unsubscribed
- case error([Error])
+ case error(JSONValue)
}
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncWebSocketClientProtocol.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncWebSocketClientProtocol.swift
index d7d9cadc29..8969aaa1e3 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncWebSocketClientProtocol.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncWebSocketClientProtocol.swift
@@ -8,7 +8,7 @@
import Foundation
import Combine
-@_spi(WebSocket) import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
protocol AppSyncWebSocketClientProtocol: AnyObject {
var isConnected: Bool { get async }
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/APIKeyAuthInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/APIKeyAuthInterceptor.swift
index f52ded490e..7174bfd414 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/APIKeyAuthInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/APIKeyAuthInterceptor.swift
@@ -8,7 +8,7 @@
import Foundation
import Amplify
-@_spi(WebSocket) import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
class APIKeyAuthInterceptor {
private let apiKey: String
@@ -31,7 +31,10 @@ extension APIKeyAuthInterceptor: WebSocketInterceptor {
extension APIKeyAuthInterceptor: AppSyncRequestInterceptor {
func interceptRequest(event: AppSyncRealTimeRequest, url: URL) async -> AppSyncRealTimeRequest {
- let host = AppSyncRealTimeClientFactory.appSyncApiEndpoint(url).host!
+ guard let host = AppSyncRealTimeClientFactory.appSyncApiEndpoint(url).host else {
+ return event
+ }
+
guard case .start(let request) = event else {
return event
}
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/AuthTokenInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/AuthTokenInterceptor.swift
index b0f19ffd78..c5ed1b50e7 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/AuthTokenInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/AuthTokenInterceptor.swift
@@ -7,7 +7,7 @@
import Foundation
import Amplify
-@_spi(WebSocket) import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
/// General purpose authenticatication subscriptions interceptor for providers whose only
/// requirement is to provide an authentication token via the "Authorization" header
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift
index c3d33320c2..41d4aa26b6 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift
@@ -6,10 +6,11 @@
//
import Foundation
-@_spi(WebSocket) import AWSPluginsCore
import Amplify
import AWSClientRuntime
import ClientRuntime
+import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
class IAMAuthInterceptor {
@@ -114,7 +115,7 @@ extension IAMAuthInterceptor: AppSyncRequestInterceptor {
return .start(.init(
id: request.id,
data: request.data,
- auth: authHeader.map { .iam($0) }
+ auth: authHeader.map { AppSyncRealTimeRequestAuth.iam($0) }
))
}
}
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift
index 44e2cf378d..09dfa9d3c7 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift
@@ -147,8 +147,8 @@ public class AWSGraphQLSubscriptionTaskRunner: InternalTaskRunner,
case .unsubscribed:
send(GraphQLSubscriptionEvent.connection(.disconnected))
finish()
- case .error(let errors):
- fail(toAPIError(errors, type: R.self))
+ case .error(let payload):
+ fail(toAPIError(Self.decodeAppSyncRealTimeResponseError(payload), type: R.self))
}
}
@@ -178,6 +178,38 @@ public class AWSGraphQLSubscriptionTaskRunner: InternalTaskRunner,
}
}
+ internal static func decodeAppSyncRealTimeResponseError(_ data: JSONValue?) -> [Error] {
+ let knownAppSyncRealTimeRequestErorrs =
+ decodeAppSyncRealTimeRequestError(data)
+ .filter { !$0.isUnknown }
+ if knownAppSyncRealTimeRequestErorrs.isEmpty {
+ let graphQLErrors = decodeGraphQLErrors(data)
+ return graphQLErrors.isEmpty
+ ? [APIError.operationError("Failed to decode AppSync error response", "", nil)]
+ : graphQLErrors
+ } else {
+ return knownAppSyncRealTimeRequestErorrs
+ }
+ }
+
+ internal static func decodeGraphQLErrors(_ data: JSONValue?) -> [GraphQLError] {
+ do {
+ return try GraphQLErrorDecoder.decodeAppSyncErrors(data)
+ } catch {
+ print("Failed to decode errors: \(error)")
+ return []
+ }
+ }
+
+ internal static func decodeAppSyncRealTimeRequestError(_ data: JSONValue?) -> [AppSyncRealTimeRequest.Error] {
+ guard let errorsJson = data?.errors else {
+ print("No 'errors' field found in response json")
+ return []
+ }
+ let errors = errorsJson.asArray ?? [errorsJson]
+ return errors.compactMap(AppSyncRealTimeRequest.parseResponseError(error:))
+ }
+
}
// Class is still necessary. See https://github.com/aws-amplify/amplify-swift/issues/2252
@@ -329,8 +361,11 @@ final public class AWSGraphQLSubscriptionOperation: GraphQLSubscri
dispatchInProcess(data: GraphQLSubscriptionEvent.connection(.disconnected))
dispatch(result: .successfulVoid)
finish()
- case .error(let errors):
- dispatch(result: .failure(toAPIError(errors, type: R.self)))
+ case .error(let payload):
+ dispatch(result: .failure(toAPIError(
+ AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeResponseError(payload),
+ type: R.self
+ )))
finish()
}
}
@@ -438,3 +473,4 @@ fileprivate func toAPIError(_ errors: [Error], type: R.Type) -> AP
}
#endif
}
+
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift
index 1666312feb..3f8a7a5d5e 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift
@@ -9,7 +9,9 @@
import Foundation
import Amplify
import Combine
-@_spi(WebSocket) import AWSPluginsCore
+import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
+
protocol AppSyncRealTimeClientFactoryProtocol {
func getAppSyncRealTimeClient(
@@ -21,13 +23,6 @@ protocol AppSyncRealTimeClientFactoryProtocol {
) async throws -> AppSyncRealTimeClientProtocol
}
-protocol AppSyncRealTimeClientProtocol {
- func connect() async throws
- func disconnectWhenIdel() async
- func disconnect() async
- func subscribe(id: String, query: String) async throws -> AnyPublisher
- func unsubscribe(id: String) async throws
-}
actor AppSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol {
struct MapperCacheKey: Hashable {
diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift
index 5e1ed6b31c..3966917b5e 100644
--- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift
+++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift
@@ -10,7 +10,8 @@ import XCTest
import Combine
@testable import Amplify
@testable import AWSAPIPlugin
-@testable @_spi(WebSocket) import AWSPluginsCore
+@testable import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
class AppSyncRealTimeClientTests: XCTestCase {
let subscriptionRequest = """
@@ -154,10 +155,12 @@ class AppSyncRealTimeClientTests: XCTestCase {
maxSubscriptionReachedError.assertForOverFulfill = false
let retryTriggerredAndSucceed = expectation(description: "Retry on max subscription reached error and succeed")
cancellables.append(try await makeOneSubscription { event in
- if case .error(let errors) = event {
- XCTAssertTrue(errors.count == 1)
- XCTAssertTrue(errors[0] is AppSyncRealTimeRequest.Error)
- if case .maxSubscriptionsReached = errors[0] as! AppSyncRealTimeRequest.Error {
+ if case .error(let payload) = event,
+ let error = payload.errors {
+ let errors = error.asArray ?? [error]
+ let requestErrors = errors.compactMap(AppSyncRealTimeRequest.parseResponseError(error:))
+ XCTAssertTrue(requestErrors.count == 1)
+ if case .maxSubscriptionsReached = requestErrors[0] {
maxSubscriptionReachedError.fulfill()
cancellables.dropLast(10).forEach { $0?.cancel() }
}
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift
index 279ca304d3..dc68e851cc 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift
@@ -9,8 +9,8 @@
import XCTest
import Combine
import Amplify
-@_spi(WebSocket) import AWSPluginsCore
@testable import AWSAPIPlugin
+@_spi(WebSocket) import InternalAmplifyNetwork
class AppSyncRealTimeClientTests: XCTestCase {
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/SubscriptionInterceptor/CognitoAuthInterceptorTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/SubscriptionInterceptor/CognitoAuthInterceptorTests.swift
index 4127f018fd..827ed88dac 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/SubscriptionInterceptor/CognitoAuthInterceptorTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/SubscriptionInterceptor/CognitoAuthInterceptorTests.swift
@@ -9,7 +9,7 @@
import XCTest
import Amplify
@testable import AWSAPIPlugin
-@testable @_spi(WebSocket) import AWSPluginsCore
+@testable import AWSPluginsCore
class CognitoAuthInterceptorTests: XCTestCase {
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift
index 2ba9f97779..9840a28170 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift
@@ -10,7 +10,8 @@ import Foundation
import Amplify
import Combine
@testable import AWSAPIPlugin
-@_spi(WebSocket) import AWSPluginsCore
+@testable import AWSPluginsCore
+@_spi(WebSocket) import InternalAmplifyNetwork
struct MockSubscriptionConnectionFactory: AppSyncRealTimeClientFactoryProtocol {
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift
index 95fc5b8e63..f0b9dcf390 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionOperationCancelTests.swift
@@ -10,7 +10,7 @@ import XCTest
@testable import Amplify
@testable import AWSAPIPlugin
@testable import AmplifyTestCommon
-@testable @_spi(WebSocket) import AWSPluginsCore
+@testable import AWSPluginsCore
@testable import AWSPluginsTestCommon
// swiftlint:disable:next type_name
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerTests.swift
similarity index 66%
rename from AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift
rename to AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerTests.swift
index a06001515f..f96b8fe5bd 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerCancelTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLSubscriptionTaskRunnerTests.swift
@@ -14,7 +14,7 @@ import XCTest
@testable import AWSPluginsTestCommon
// swiftlint:disable:next type_name
-class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase {
+class AWSGraphQLSubscriptionTaskRunnerTests: XCTestCase {
var apiPlugin: AWSAPIPlugin!
var authService: MockAWSAuthService!
var pluginConfig: AWSAPICategoryPluginConfiguration!
@@ -183,4 +183,102 @@ class AWSGraphQLSubscriptionTaskRunnerCancelTests: XCTestCase {
subscriptionEvents.cancel()
await fulfillment(of: [receivedFailure, receivedCompletion], timeout: 5)
}
+
+ func testDecodeAppSyncRealTimeResponseError_withEmptyJsonValue_failedToDecode() {
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeResponseError(nil)
+ XCTAssertEqual(errors.count, 1)
+ if case .some(.operationError(let description, _, _)) = errors.first as? APIError {
+ XCTAssertTrue(description.contains("Failed to decode AppSync error response"))
+ } else {
+ XCTFail("Should be failed with APIError")
+ }
+ }
+
+ func testDecodeAppSYncRealTimeResponseError_withKnownAppSyncRealTimeRequestError_returnKnownErrors() {
+ let errorJson: JSONValue = [
+ "errors": [[
+ "message": "test1",
+ "errorType": "MaxSubscriptionsReachedError"
+ ]]
+ ]
+
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeResponseError(errorJson)
+ XCTAssertEqual(errors.count, 1)
+ guard case .maxSubscriptionsReached = errors.first as? AppSyncRealTimeRequest.Error else {
+ XCTFail("Should be AppSyncRealTimeRequestError")
+ return
+ }
+ }
+
+ func testDecodeAppSYncRealTimeResponseError_withUnknownError_returnParsedGraphQLError() {
+ let errorJson: JSONValue = [
+ "errors": [[
+ "message": "test1",
+ "errorType": "Unknown"
+ ]]
+ ]
+
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeResponseError(errorJson)
+ XCTAssertEqual(errors.count, 1)
+ XCTAssertTrue(errors.first is GraphQLError)
+ }
+
+ func testDecodeAppSyncRealTimeRequestError_withoutErrorsField_returnEmptyErrors() {
+ let errorJson: JSONValue = [
+ "noErrors": [[
+ "message": "test1",
+ "errorType": "Unknown"
+ ]]
+ ]
+
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeRequestError(errorJson)
+ XCTAssertEqual(errors.count, 0)
+ }
+
+ func testDecodeAppSyncRealTimeRequestError_withWellFormatErrors_parseErrors() {
+ let errorJson: JSONValue = [
+ "errors": [[
+ "message": "test1",
+ "errorType": "MaxSubscriptionsReachedError"
+ ], [
+ "message": "test2",
+ "errorType": "LimitExceededError"
+ ], [
+ "message": "test3",
+ "errorType": "Unauthorized"
+ ]]
+ ]
+
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeRequestError(errorJson)
+ XCTAssertEqual(errors.count, 3)
+ }
+
+ func testDecodeAppSyncRealTimeRequestError_withSomeWellFormatErrors_parseErrors() {
+ let errorJson: JSONValue = [
+ "errors": [[
+ "message": "test1",
+ "errorType": "MaxSubscriptionsReachedError"
+ ], [
+ "message": "test2",
+ "errorType": "LimitExceededError"
+ ], [
+ "random": "123"
+ ]]
+ ]
+
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeRequestError(errorJson)
+ XCTAssertEqual(errors.count, 2)
+ }
+
+ func testDecodeAppSyncRealTimeRequestError_withSingletonErrors_parseErrors() {
+ let errorJson: JSONValue = [
+ "errors": [
+ "message": "test1",
+ "errorType": "MaxSubscriptionsReachedError"
+ ]
+ ]
+
+ let errors = AWSGraphQLSubscriptionTaskRunner.decodeAppSyncRealTimeRequestError(errorJson)
+ XCTAssertEqual(errors.count, 1)
+ }
}
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift
index d632e131c7..9d0257d6c7 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTaskTests.swift
@@ -130,7 +130,11 @@ class GraphQLSubscribeTasksTests: OperationTestBase {
await fulfillment(of: [onSubscribeInvoked], timeout: 0.05)
try await MockAppSyncRealTimeClient.waitForSubscirbing()
- mockAppSyncRealTimeClient?.triggerEvent(.error([AppSyncRealTimeRequest.Error.limitExceeded]))
+ mockAppSyncRealTimeClient?.triggerEvent(.error([
+ "errors": [
+ "errorType": "LimitExceededError"
+ ]
+ ]))
expectedCompletionFailureError = APIError.operationError("", "", AppSyncRealTimeRequest.Error.limitExceeded)
await waitForSubscriptionExpectations()
}
@@ -145,18 +149,21 @@ class GraphQLSubscribeTasksTests: OperationTestBase {
try await subscribe()
await fulfillment(of: [onSubscribeInvoked], timeout: 0.05)
- let unauthorizedError = GraphQLError(message: "", extensions: ["errorType": "Unauthorized"])
try await MockAppSyncRealTimeClient.waitForSubscirbing()
- mockAppSyncRealTimeClient?.triggerEvent(.error([unauthorizedError]))
+ mockAppSyncRealTimeClient?.triggerEvent(.error([
+ "errors": [
+ "errorType": "Unauthorized"
+ ]
+ ]))
expectedCompletionFailureError = APIError.operationError(
"Subscription item event failed with error: Unauthorized",
"",
- GraphQLResponseError.error([unauthorizedError])
+ AppSyncRealTimeRequest.Error.unauthorized
)
await waitForSubscriptionExpectations()
}
- func testConnectionErrorWithAppSyncConnectionError() async throws {
+ func testConnectionErrorWithOtherAppSyncConnectionError() async throws {
receivedCompletionSuccess.isInverted = true
receivedStateValueConnected.isInverted = true
receivedStateValueDisconnected.isInverted = true
@@ -167,8 +174,16 @@ class GraphQLSubscribeTasksTests: OperationTestBase {
await fulfillment(of: [onSubscribeInvoked], timeout: 0.05)
try await MockAppSyncRealTimeClient.waitForSubscirbing()
- mockAppSyncRealTimeClient?.triggerEvent(.error([URLError(URLError.Code(rawValue: 400))]))
- expectedCompletionFailureError = APIError.operationError("", "", URLError(URLError.Code(rawValue: 400)))
+ mockAppSyncRealTimeClient?.triggerEvent(.error([
+ "errors": [
+ "message": "other error"
+ ]
+ ]))
+ expectedCompletionFailureError = APIError.operationError(
+ "Subscription item event failed with error",
+ "",
+ GraphQLResponseError.error([GraphQLError(message: "other error")])
+ )
await waitForSubscriptionExpectations()
}
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/AmplifyNetworkMonitor.swift b/AmplifyPlugins/Internal/Sources/Network/WebSocket/AmplifyNetworkMonitor.swift
similarity index 97%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/AmplifyNetworkMonitor.swift
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/AmplifyNetworkMonitor.swift
index 23eb1ec4e2..e1ac3c9c5c 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/AmplifyNetworkMonitor.swift
+++ b/AmplifyPlugins/Internal/Sources/Network/WebSocket/AmplifyNetworkMonitor.swift
@@ -9,7 +9,7 @@
import Network
import Combine
-@_spi(WebSocket)
+@_spi(NetworkReachability)
public final class AmplifyNetworkMonitor {
public enum State {
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/README.md b/AmplifyPlugins/Internal/Sources/Network/WebSocket/README.md
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/README.md
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/README.md
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/RetryWithJitter.swift b/AmplifyPlugins/Internal/Sources/Network/WebSocket/RetryWithJitter.swift
similarity index 98%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/RetryWithJitter.swift
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/RetryWithJitter.swift
index 9da51cb03f..29436f916f 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/RetryWithJitter.swift
+++ b/AmplifyPlugins/Internal/Sources/Network/WebSocket/RetryWithJitter.swift
@@ -8,7 +8,7 @@
import Foundation
-@_spi(WebSocket)
+@_spi(RetryWithJitter)
public actor RetryWithJitter {
public enum Error: Swift.Error {
case maxRetryExceeded([Swift.Error])
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketClient.swift b/AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketClient.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketClient.swift
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketClient.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketEvent.swift b/AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketEvent.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketEvent.swift
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketEvent.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketInterceptor.swift b/AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketInterceptor.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketInterceptor.swift
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketInterceptor.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketNetworkMonitorProtocol.swift b/AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketNetworkMonitorProtocol.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/WebSocket/WebSocketNetworkMonitorProtocol.swift
rename to AmplifyPlugins/Internal/Sources/Network/WebSocket/WebSocketNetworkMonitorProtocol.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/LocalWebSocketServer.swift b/AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/LocalWebSocketServer.swift
similarity index 93%
rename from AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/LocalWebSocketServer.swift
rename to AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/LocalWebSocketServer.swift
index 1dc0fbd948..0b8c47f16a 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/LocalWebSocketServer.swift
+++ b/AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/LocalWebSocketServer.swift
@@ -37,7 +37,7 @@ class LocalWebSocketServer {
stack.applicationProtocols.insert(ws, at: 0)
let port = NWEndpoint.Port(rawValue: portNumber)!
guard let listener = try? NWListener(using: params, on: port) else {
- throw "unable to start the listener at: localhost:\(port)"
+ throw LocalWebSocketServerError.error("unable to start the listener at: localhost:\(port)")
}
listener.newConnectionHandler = { [weak self] conn in
@@ -93,7 +93,7 @@ class LocalWebSocketServer {
func sendTransientFailureToConnections() {
self.connections.forEach {
- var metadata = NWProtocolWebSocket.Metadata(opcode: .close)
+ let metadata = NWProtocolWebSocket.Metadata(opcode: .close)
metadata.closeCode = .protocolCode(NWProtocolWebSocket.CloseCode.Defined.internalServerError)
$0.send(
content: nil,
@@ -103,3 +103,7 @@ class LocalWebSocketServer {
}
}
}
+
+enum LocalWebSocketServerError: Error {
+ case error(String)
+}
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/RetryWithJitterTests.swift b/AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/RetryWithJitterTests.swift
similarity index 96%
rename from AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/RetryWithJitterTests.swift
rename to AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/RetryWithJitterTests.swift
index 9ada954056..d1bbe4a22c 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/RetryWithJitterTests.swift
+++ b/AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/RetryWithJitterTests.swift
@@ -7,7 +7,8 @@
import XCTest
-@testable @_spi(WebSocket) import AWSPluginsCore
+@testable import AWSPluginsCore
+@testable @_spi(RetryWithJitter) import InternalAmplifyNetwork
class RetryWithJitterTests: XCTestCase {
struct TestError: Error {
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/WebSocketClientTests.swift b/AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/WebSocketClientTests.swift
similarity index 98%
rename from AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/WebSocketClientTests.swift
rename to AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/WebSocketClientTests.swift
index f3e53669c1..51b7a2e8f9 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/WebSocket/WebSocketClientTests.swift
+++ b/AmplifyPlugins/Internal/Tests/NetworkTests/WebSocket/WebSocketClientTests.swift
@@ -8,7 +8,8 @@
import XCTest
import Combine
-@testable @_spi(WebSocket) import AWSPluginsCore
+@testable import AWSPluginsCore
+@testable @_spi(WebSocket) @_spi(NetworkReachability) import InternalAmplifyNetwork
fileprivate let timeout: TimeInterval = 5
diff --git a/Package.swift b/Package.swift
index 25639dce9c..bc4ce0b4a5 100644
--- a/Package.swift
+++ b/Package.swift
@@ -115,7 +115,8 @@ let apiTargets: [Target] = [
name: "AWSAPIPlugin",
dependencies: [
.target(name: "Amplify"),
- .target(name: "AWSPluginsCore")
+ .target(name: "AWSPluginsCore"),
+ .target(name: "InternalAmplifyNetwork")
],
path: "AmplifyPlugins/API/Sources/AWSAPIPlugin",
exclude: [
@@ -315,6 +316,24 @@ let internalPinpointTargets: [Target] = [
)
]
+let internalNetworkingTargets: [Target] = [
+ .target(
+ name: "InternalAmplifyNetwork",
+ dependencies: [
+ .target(name: "Amplify")
+ ],
+ path: "AmplifyPlugins/Internal/Sources/Network"
+ ),
+ .testTarget(
+ name: "InternalAmplifyNetworkUnitTests",
+ dependencies: [
+ "AmplifyTestCommon",
+ "InternalAmplifyNetwork"
+ ],
+ path: "AmplifyPlugins/Internal/Tests/NetworkTests"
+ )
+]
+
let analyticsTargets: [Target] = [
.target(
name: "AWSPinpointAnalyticsPlugin",
@@ -443,6 +462,7 @@ let targets: [Target] = amplifyTargets
+ analyticsTargets
+ pushNotificationsTargets
+ internalPinpointTargets
+ + internalNetworkingTargets
+ predictionsTargets
+ loggingTargets