AppSync subscriptions with ApolloClient in React

AppSync subscriptions with ApolloClient in React


8

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    idn    released_daten    __typenamen  }n}n"}}
{"id":"2","type":"start","payload":{"variables":{},"extensions":{},"operationName":"questionReleased","query":"subscription questionReleased {n  questionReleased {n    idn    released_daten    __typenamen  }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!

2 Answers
2


15

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.

3

  • 5

    This should be updated in 2021 to reflect the deprecation of MQTT support by AppSync. createSubscriptionHandshakeLink(url, httpLink) will no longer work and instead, you must use createSubscriptionHandshakeLink({ url, region, auth })

    – Joe

    Jul 13, 2021 at 12:05

  • I don't suppose you ever got this working with apollo v2?

    – cphilpot

    Mar 23, 2022 at 21:58

  • Sorry, I'm having the same issue and I didn't quiet get it. Did you end up using AWSAppSyncClient or the link strategy? Could someone present the complete client connection?

    – André Guerra

    Apr 12, 2022 at 9:35


0

It would help you https://gist.github.com/wellitongervickas/087fb0d0550c429aae4500e4e4e9f624

library is not implement the payload data properly, just take a look around the following code:

 Object.assign(operation, {
    data: JSON.stringify({
      query: operation.query.loc?.source.body,
      variables: operation.variables
    })
  })

it will include your missing props



Leave a Reply

Your email address will not be published. Required fields are marked *