Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: amplify network package #3583

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
20 changes: 20 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,26 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AmplifyNetworkUnitTests"
BuildableName = "AmplifyNetworkUnitTests"
BlueprintName = "AmplifyNetworkUnitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "InternalAmplifyNetworkUnitTests"
BuildableName = "InternalAmplifyNetworkUnitTests"
BlueprintName = "InternalAmplifyNetworkUnitTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@


import Foundation
import Amplify
import Combine
@_spi(WebSocket) import AWSPluginsCore
import Amplify
@_spi(WebSocket) @_spi(RetryWithJitter) import InternalAmplifyNetwork

protocol AppSyncRealTimeClientProtocol {
func connect() async throws
func disconnectWhenIdel() async
func disconnect() async
func subscribe(id: String, query: String) async throws -> AnyPublisher<AppSyncSubscriptionEvent, Never>
func unsubscribe(id: String) async throws
}

/**
The AppSyncRealTimeClient conforms to the AppSync real-time WebSocket protocol.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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? {
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation
import Combine
@_spi(WebSocket) import AWSPluginsCore
@_spi(WebSocket) import InternalAmplifyNetwork

protocol AppSyncWebSocketClientProtocol: AnyObject {
var isConnected: Bool { get async }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation
import Amplify
@_spi(WebSocket) import AWSPluginsCore
@_spi(WebSocket) import InternalAmplifyNetwork

class APIKeyAuthInterceptor {
private let apiKey: String
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
//

import Foundation
@_spi(WebSocket) import AWSPluginsCore
import Amplify
import AWSClientRuntime
import ClientRuntime
import AWSPluginsCore
@_spi(WebSocket) import InternalAmplifyNetwork

class IAMAuthInterceptor {

Expand Down Expand Up @@ -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) }
))
}
}
Loading
Loading