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

AWS AppSync client #241

Closed
fab-mindflow opened this issue Oct 12, 2021 · 11 comments
Closed

AWS AppSync client #241

fab-mindflow opened this issue Oct 12, 2021 · 11 comments
Labels
question Further information about the library is requested

Comments

@fab-mindflow
Copy link

Not sure whether it is / can be supported. I've quickly tried and abandoned temporarily as it fails connecting requiring a few change to headers and more.

Story

As a AWS AppSync client developer

I want to have a guide (or necessary boilerplate) for subscribing to AppSync real-time updates

So that I can easily replace other cumbersome client libraries like AWS Amplify or AppSync SDK with graphql-ws

Acceptance criteria

  • client is able to subscribe to AWS AppSync real-time update
  • client is able to authenticate with AWS Cognito
  • client is able to define the necessary configuration
@enisdenjo
Copy link
Owner

enisdenjo commented Oct 12, 2021

Sadly AWS AppSync supports only the legacy graphql-ws subprotocol from subscriptions-transport-ws. This library is not backwards-compatible (and will never be) with the legacy subprotocol.

You could either create a ticket with AWS, or consider the following alternatives for their environment:

@enisdenjo enisdenjo added the question Further information about the library is requested label Oct 12, 2021
@fab-mindflow
Copy link
Author

Thanks for the prompt answer. That's what I was afraid of as I've modified the header to comply with the one expected by AppSync - and of course failing during init 😁. OK that's very clear until someone at AWS decides to upgrade. Thanks for the recommendation too.

@dmitryame
Copy link

dmitryame commented Jan 17, 2022

Just curious, have there been any updates from AWS yet (if anyone knows)? AWS AppSync client is badly needed, I mean the one which works correctly with subscriptions via WebSockets.

@ebisbe
Copy link

ebisbe commented Feb 13, 2022

@dmitryame After a few days of searching, reading and testing I can conclude that I make it work with this implementation apollographql/apollo-feature-requests#224 (comment) . Using appsync, apollo and in my case for Vue.

There's an issue. The example uses the API key instead of Cognito pools for validation and I have to find a way to access the URL and modify it where I can await as I need to resolve (await Auth.currentSession()).getIdToken().getJwtToken() to get a valid token from each session user.

@half2me
Copy link

half2me commented Mar 8, 2023

Is this still the case? Is graphql-ws not supported in AppSync?

@dmitryame
Copy link

I don't think so.
Here is the solution that worked for me https://github.com/echowaves/placechatter/blob/main/src/subscriptionClientWs.js
Hope you find it useful.

@half2me
Copy link

half2me commented Mar 8, 2023

I don't think so.
Here is the solution that worked for me https://github.com/echowaves/placechatter/blob/main/src/subscriptionClientWs.js
Hope you find it useful.

this seems to use the depracated subscriptions-transport-ws though :(

@dmitryame
Copy link

dmitryame commented Mar 8, 2023 via email

@half2me
Copy link

half2me commented Mar 8, 2023

@dmitryame yeah well, I'm not using the apollo client, so I'm not sure how much of this I need etc. I'm using https://houdinigraphql.com and they have support for a number of transports, graphql-ws and even subscriptions-transport-ws being among them, but I'm not sure which one should be used.

@vaishal
Copy link

vaishal commented Jul 29, 2024

I managed to get graphql-ws working with AppSync based off some of the solutions listed above.
I’m using AppSync with api-key auth and URQL for the GQL client.

import { createClient } from "graphql-ws";

const base64 = (object: Record<string, unknown>) => {
  return Buffer.from(JSON.stringify(object)).toString("base64");
};

export const appSyncWSUrl = () => {
  const url = new URL(process.env.APPSYNC_REALTIME_URL);

  return `${url}?header=${base64({
    host: url.host,
    "x-api-key": process.env.APPSYNC_API_KEY,
  })}&payload=${base64({})}`;
};

export const appSyncWSClient = createClient({
  url: appSyncWSUrl(),

  // AppSync requires `graphql-ws` protocol
  webSocketImpl: class extends WebSocket {
    constructor(url: URL) {
      super(url, "graphql-ws");
    }
  },
  jsonMessageReplacer: (key, value) => {
    if (key === "type" && value === "subscribe") {
      return "start";
    }

    return value;
  },
  jsonMessageReviver: (key, value) => {
    if (key === "type") {
      if (value === "start") {
        return "subscribe";
      }

      if (value === "start_ack" || value === "ka") {
        return "connection_ack";
      }

      if (value === "data") {
        return "next";
      }
    }

    return value;
  },
});

// URQL subscriptionExchange

forwardSubscription: (request, operation) => {
  const input = {
    query: request.query || "",
    data: JSON.stringify({
      query: request.query || "",
      variables: request.variables,
    }),
    extensions: {
      ...request.extensions,
      authorization: {
        host: new URL(process.env.APPSYNC_REALTIME_URL).host,
        "x-api-key": process.env.APPSYNC_API_KEY,
      },
    },
  };

  return {
    subscribe: sink => {
      const unsubscribe = appSyncWSClient.subscribe(input, sink);

      return { unsubscribe };
    },
  };
};

@ret-smart
Copy link

var subscription = null;

// subscribe to data point update
function subscribeDatapoints(token) {
    if(!subscription && token) {
        const client = createClient({
            url: speech.ws_uri,
    
            // AppSync requires `graphql-ws` protocol and auth
            webSocketImpl: class extends WebSocket {
                constructor(url) {
                    super(url, 'graphql-ws', {
                        headers: {
                            host: new URL(awsconfig.aws_appsync_graphqlEndpoint).hostname,
                            authorization: 'Bearer ' + token
                        }
                    });
                }
            },
            jsonMessageReplacer: (key, value) => {
                if (key === "type" && value === "subscribe") {
                    return "start";
                }
        
                return value;
            },
            jsonMessageReviver: (key, value) => {
                console.log(getTime(), "message=", key, value);

                if (key === "type") {
                    if (value === "start") {
                        return "subscribe";
                    }
        
                    if (value === "start_ack" || value === "ka") {
                        return "connection_ack";
                    }
        
                    if (value === "data") {
                        return "next";
                    }

                    if( value === "connection_error") {
                        return "error";
                    }
                }
        
                return value;
            }
        });

        const payload = {
            query: custom.onUpdateDatapoint,
            variables: {},
            data: JSON.stringify({
                query: custom.onUpdateDatapoint,
                variables: {},
            }),
            extensions: {
                authorization: {
                    host: new URL(awsconfig.aws_appsync_graphqlEndpoint).hostname,
                    authorization: 'Bearer ' + token
                }
            }
        }

        subscription = client.subscribe(payload, {
            next: (data) => {
                console.log(getTime(), "data=", data);
                const db = data.data.onUpdateDatapoint;
                sendValue( token, db.id, db.value);
            },
            error: (err) => console.error(getTime(), 'error=', err),
            complete: () => console.log(getTime(), 'subscription complete'),
        });
    }

    console.log(getTime(), "subscription=", subscription)
    return subscription;
}

function unsubscribeDatapoints() {
    if( subscription) {
        subscription();
        subscription = null;
    }
}
`

This is my adaption with auth token for nodejs 18/20.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information about the library is requested
Projects
None yet
Development

No branches or pull requests

7 participants