I'm currently using ApolloClient to connect to an AppSync GraphQL API. It all works perfectly for queries and mutations, but I'm having some trouble getting subscriptions to work. I've followed the Apollo docs and my App.js looks like this:
import React from 'react';
import './App.css';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from '@apollo/react-hooks';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink, split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { createAuthLink } from 'aws-appsync-auth-link';
import { createHttpLink } from 'apollo-link-http';
import AWSAppSyncClient, { AUTH_TYPE } from "aws-appsync";
import { useSubscription } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';
const url = "https://xxx.appsync-api.eu-west-2.amazonaws.com/graphql"
const realtime_url = "wss://xxx.appsync-realtime-api.eu-west-2.amazonaws.com/graphql"
const region = "eu-west-2";
const auth = {
type: AUTH_TYPE.API_KEY,
apiKey: process.env.REACT_APP_API_KEY
};
const wsLink = new WebSocketLink({
uri: realtime_url,
options: {
reconnect: true
},
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
ApolloLink.from([
createAuthLink({ realtime_url, region, auth }),
wsLink
]),
ApolloLink.from([
createAuthLink({ url, region, auth }),
createHttpLink({ uri: url })
])
);
const client = new ApolloClient({
link: link,
cache: new InMemoryCache({
dataIdFromObject: object => object.id,
}),
});
function Page() {
const { loading, error, data } = useSubscription(
gql`
subscription questionReleased {
questionReleased {
id
released_date
}
}
`
)
if (loading) return <span>Loading...</span>
if (error) return <span>Error!</span>
if (data) console.log(data)
return (
<div>{data}</div>
);
}
function App() {
return (
<ApolloProvider client={client}>
<div className="App">
<Page />
</div>
</ApolloProvider>
);
}
export default App;
If I go to the network tab in web inspector, I can see the request:
wss://xxx.appsync-realtime-api.eu-west-2.amazonaws.com/graphql
And the messages:
{"type":"connection_init","payload":{}}
{"id":"1","type":"start","payload":{"variables":{},"extensions":{},"operationName":"questionReleased","query":"subscription questionReleased {\n questionReleased {\n id\n released_date\n __typename\n }\n}\n"}}
{"id":"2","type":"start","payload":{"variables":{},"extensions":{},"operationName":"questionReleased","query":"subscription questionReleased {\n questionReleased {\n id\n released_date\n __typename\n }\n}\n"}}
{"payload":{"errors":[{"message":"Both, the \"header\", and the \"payload\" query string parameters are missing","errorCode":400}]},"type":"connection_error"}
I've searched around a lot and it seems that ApolloClient may not be compatible with AppSync subscriptions - is anybody able to confirm this?
So as an alternative I've tried to use AWSAppSyncClient
for subscriptions:
function Page() {
const aws_client = new AWSAppSyncClient({
region: "eu-west-2",
url: realtime_url,
auth: {
type: AUTH_TYPE.API_KEY,
apiKey: process.env.REACT_APP_API_KEY
},
disableOffline: true
});
const { loading, error, data } = useSubscription(
gql`
subscription questionReleased {
questionReleased {
id
released_date
}
}
`,
{client: aws_client}
)
if (loading) return <span>Loading...</span>
if (error) return <span>Error!</span>
if (data) console.log(data)
return (
<div>{data}</div>
);
}
It now sends querystrings with the request:
wss://xxx.appsync-realtime-api.eu-west-2.amazonaws.com/graphql?header=eyJob3N0I...&payload=e30=
And I now get a different error:
{"type":"connection_init"}
{"payload":{"errors":[{"errorType":"HttpNotFoundException"}]},"type":"connection_error"}
I've double checked the url and it's ok (if it's not you get ERR_NAME_NOT_RESOLVED
). The subscription works when I run it manually through the AppSync console, so that should also be ok.
I've also tried .hydrated()
on the aws_client
but get another error (TypeError: this.refreshClient(...).client.subscribe is not a function
)
What am I doing wrong? This has been driving me nuts for a few days!
AppSync subscriptions allow you to push events to clients in real-time when a change happened. This is great for applications that show data that can change without user interaction, which is the case for almost all applications.
Open your favorite browser and navigate to http://localhost:3000/graphql, you will see the playground UI appear, so run the subscription. The playground will start listening to event updates from the GraphQL server. Create a second tab and load http://localhost:3000/graphql on it.
Subscriptions are usually implemented with WebSockets, where the server holds a steady connection to the client. This means when working with subscriptions, we're breaking the Request-Response cycle that is typically used for interactions with the API.
I finally figured it out not long after posting. I'll put the solution here in case anyone else runs into the same issues. Firstly AWSAppSyncClient
should take the main graphql url instead of the real-time url - it can work out the real-time url itself. However I still couldn't get this working with the useSubscription
hook, only by calling aws_client.subscribe()
.
To get it working with the useSubscription
hook, I found the solution mentioned in this discussion:
https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/450
Specifically this: https://github.com/awslabs/aws-mobile-appsync-sdk-js#using-authorization-and-subscription-links-with-apollo-client-no-offline-support
The relevant code is:
import { createSubscriptionHandshakeLink } from 'aws-appsync-subscription-link';
const httpLink = createHttpLink({ uri: url })
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createSubscriptionHandshakeLink(url, httpLink)
]);
After using that, everything works perfectly for me.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With