How do i use Apollo-IOS Subscriptions with AWS Appsync

Hi,

Im pretty new to the whole GraphQL, AWS Appsync eco system.

I have an IOS app that i want to connect to AWS Appsync and use their functionality.

I have managed to setup Apollo-IOS so that i can do the basic CRUD operations.
But i want to get subscriptions working as well. I followed the basic guide with authentication that is available in the Apollo-IOS documentation.

That guide didn’t really work for me. Se below setup of the Apollo Client:

class Network {
  
    static let shared = Network()
        
    private(set) lazy var apollo: ApolloClient = {
        let client = URLSessionClient()
        let cache = InMemoryNormalizedCache()
        let store = ApolloStore(cache: cache)
        let provider = NetworkInterceptorProvider(client: client, store: store)
        let url = URL(string: "https://someURL.appsync-api.eu-west-1.amazonaws.com/graphql")!
        let transport = RequestChainNetworkTransport(interceptorProvider: provider,
                                                     endpointURL: url)
        
        let socketURL = URL(string: "wss://someURL.appsync-realtime-api.eu-west-1.amazonaws.com/graphql")!
        var request = URLRequest(url: socketURL)
        let webSocketClient = DefaultWebSocket(request: request)
        let authPayload = ["x-api-key": "someAPIKey", "host:" : "someURL.appsync-api.eu-west-1.amazonaws.com"]
        let webSocketTransport = WebSocketTransport(websocket: webSocketClient, connectingPayload: authPayload)
        
        let splitNetworkTransport = SplitNetworkTransport(
          uploadingNetworkTransport: transport,
          webSocketNetworkTransport: webSocketTransport
        )
        
        return ApolloClient(networkTransport: splitNetworkTransport, store: store)
    }()
}

This setup resulted in the following errors:

Websocket error: Unprocessed message {"payload":{"errors":[{"message":"Both, the \"header\", and the \"payload\" query string parameters are missing","errorCode":400}]},"type":"connection_error"}. Error: nil

So i read up on how the authentication works for the subscriptions on the AWS Appsync API and came up with this solution:

import Apollo
import Foundation

class Network {
  
    static let shared = Network()
        
    private(set) lazy var apollo: ApolloClient = {
        let client = URLSessionClient()
        let cache = InMemoryNormalizedCache()
        let store = ApolloStore(cache: cache)
        let provider = NetworkInterceptorProvider(client: client, store: store)
        let url = URL(string: "https://someURL.appsync-api.eu-west-1.amazonaws.com/graphql")!
        let transport = RequestChainNetworkTransport(interceptorProvider: provider,
                                                     endpointURL: url)
        
        var urlWithQueries = URLComponents(string: "wss:/someURL.appsync-realtime-api.eu-west-1.amazonaws.com/graphql")
        urlWithQueries!.queryItems = [URLQueryItem(name: "header", value: String("{\"host\":\"someURL.appsync-api.eu-west-1.amazonaws.com\",\"x-api-key\":\"someAPIKey\"}").toBase64URL()), URLQueryItem(name:"payload", value: String("{}").toBase64URL())]
        let socketURL = urlWithQueries!.url!
        var request = URLRequest(url: socketURL)
        let webSocketClient = DefaultWebSocket(request: request)
        let webSocketTransport = WebSocketTransport(websocket: webSocketClient)
        
        let splitNetworkTransport = SplitNetworkTransport(
          uploadingNetworkTransport: transport,
          webSocketNetworkTransport: webSocketTransport
        )
        
        return ApolloClient(networkTransport: splitNetworkTransport, store: store)
    }()
}

extension String {
    func toBase64URL() -> String {
        var result = Data(self.utf8).base64EncodedString()
        result = result.replacingOccurrences(of: "+", with: "-")
        return result
    }
}

class NetworkInterceptorProvider: DefaultInterceptorProvider {
    override func interceptors<Operation: GraphQLOperation>(for operation: Operation) -> [ApolloInterceptor] {
        var interceptors = super.interceptors(for: operation)
        interceptors.insert(TokenAddingInterceptor(), at: 0)
        return interceptors
    }
}


class TokenAddingInterceptor: ApolloInterceptor {
    func interceptAsync<Operation: GraphQLOperation>(
        chain: RequestChain,
        request: HTTPRequest<Operation>,
        response: HTTPResponse<Operation>?,
        completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void) {
        
        request.addHeader(name: "x-api-key", value: "someAPIKey")
        
        chain.proceedAsync(request: request,
                         response: response,
                         completion: completion)
    }
}

The above solution seems to work when it comes to authentication and so on, i don’t get the missing headers error anymore. Instead i get the following error when trying to start a subscription:

Websocket error: Unprocessed message {"type":"error","payload":{"errors":[{"errorType":"UnsupportedOperation","message":"unknown not supported through the realtime channel"}]}}. Error: nil

I have made a fair amount of research and i found that there is a similar issue on the JS version.
But that issue was placed a year ago and is still open.

I also found one guy on here that seemed to have the same problem as i am having right now, but could not really understand how he solved it.

Is there anyone here that could please help me solve this as i am wanting to use Apollo-IOS in my project as the Amplify framework does not seem to fit my needs.

This is my first time posting in forums like this, so please just let me know if i have to provide more information.

I ended that with this solution.