-
Notifications
You must be signed in to change notification settings - Fork 7
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
Document how to use client with AppSync #224
Comments
I tried this and always getting this as first message from the server : payload: {errors: [{message: "json: cannot unmarshal object into Go value of type string", errorCode: 400}]} any idea ? |
@Bariah96 Are you using IAM authentication ? I remember having a similar issue, it was regarding the signing of the messages as far as I remember |
@StefanSmith super nice that you posted this, helped me along quite a bit :-) ! I noticed that you might be able to get rid of the import { SubscriptionClient } from 'subscriptions-transport-ws';
import { v4 as uuid4 } from 'uuid';
const asBase64EncodedJson = (data: $Object): string =>
btoa(JSON.stringify(data));
// @ts-ignore
export default class UUIDOperationIdSubscriptionClient extends SubscriptionClient {
authFunction;
originalUrl;
constructor(url, args, authFunction) {
super(url, args);
this.authFunction = authFunction;
this.originalUrl = url;
}
connect = async () => {
const authInfo = await this.authFunction();
/** @see https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#iam */
// @ts-ignore
this.url = `${this.originalUrl}?header=${asBase64EncodedJson(
authInfo,
)}&payload=${asBase64EncodedJson({})}`;
// @ts-ignore
super.connect();
};
generateOperationId() {
return uuid4();
}
processReceivedData(receivedData) {
try {
const parsedMessage = JSON.parse(receivedData);
if (parsedMessage?.type === 'start_ack') return;
} catch (e) {
throw new Error('Message must be JSON-parsable. Got: ' + receivedData);
}
// @ts-ignore
super.processReceivedData(receivedData);
}
} You just have to adjust it to generate the correct auth string. |
@philiiiiiipp I'm using Cognito user pools (jwt) for authentication. Anyway, i figured out what was happening, my connection url contained a fulfilled promise object since the function getting the authentication details is async and wasn't waiting on it, while it should be a string. Thanks for replying :) |
Minimalist solution for API_KEY authDerived from the code above but for the simple, default case of API_KEY authentication, which is fixed, and without the split link to support mutations and queries over http; in production code you would copy those from the original solution above. const { ApolloClient, InMemoryCache, gql } = require("@apollo/client");
const { WebSocketLink } = require('@apollo/client/link/ws');
const WebSocket = require('ws');
const API_URL = "https://<secret>.appsync-api.eu-west-1.amazonaws.com/graphql"
const API_KEY = "da2-<secret>"
const WSS_URL = API_URL.replace('https','wss').replace('appsync-api','appsync-realtime-api')
const HOST = API_URL.replace('https://','').replace('/graphql','')
const api_header = {
'host': HOST,
'x-api-key': API_KEY
}
const header_encode = obj => btoa(JSON.stringify(obj));
const connection_url = WSS_URL + '?header=' + header_encode(api_header) + '&payload=' + header_encode({})
//------------------------------------------------------------------------------------------------
const {SubscriptionClient} = require("subscriptions-transport-ws");
const uuid4 = require("uuid").v4;
class UUIDOperationIdSubscriptionClient extends SubscriptionClient {
generateOperationId() {
// AppSync recommends using UUIDs for Subscription IDs but SubscriptionClient uses an incrementing number
return uuid4();
}
processReceivedData(receivedData) {
try {
const parsedMessage = JSON.parse(receivedData);
if (parsedMessage?.type === 'start_ack') return; // sent by AppSync but meaningless to us
} catch (e) {
throw new Error('Message must be JSON-parsable. Got: ' + receivedData);
}
super.processReceivedData(receivedData);
}
}
// appSyncGraphQLOperationAdapter.js
const graphqlPrinter = require("graphql/language/printer");
const createAppSyncGraphQLOperationAdapter = () => ({
applyMiddleware: async (options, next) => {
// AppSync expects GraphQL operation to be defined as a JSON-encoded object in a "data" property
options.data = JSON.stringify({
query: typeof options.query === 'string' ? options.query : graphqlPrinter.print(options.query),
variables: options.variables
});
// AppSync only permits authorized operations
options.extensions = {'authorization': api_header};
// AppSync does not care about these properties
delete options.operationName;
delete options.variables;
// Not deleting "query" property as SubscriptionClient validation requires it
next();
}
});
// WebSocketLink
const wsLink = new WebSocketLink(
new UUIDOperationIdSubscriptionClient(
connection_url,
{timeout: 5 * 60 * 1000, reconnect: true, lazy: true, connectionCallback: (err) => console.log("connectionCallback", err ? "ERR" : "OK", err || "")},
WebSocket
).use([createAppSyncGraphQLOperationAdapter()])
);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: wsLink,
}); |
Not sure if this will address your issue, but I can cross-post my current solution from awslabs/aws-mobile-appsync-sdk-js#448 (comment) here: https://gist.github.com/razor-x/e19d7d776cdf58d04af1e223b0757064 |
@razor-x thanks man for the reply, appreciate it, but I ended up using Once again, thanks for the reply and help. |
Thanks for the suggestion! If anyone is interested in working on a docs PR for this (in https://github.com/apollographql/apollo-client), that would be awesome! |
With the latest upgrade tried setting up and running appsync subscriptions with apollo client - https://github.com/kodehash/appsync-nodejs-apollo-client/tree/master |
Thanks for this. Saved my day 👍 |
@holyjak - Were you able to execute subscription using client.subscribe() after using above solution? I saw your were seeing issues earlier (https://community.apollographql.com/t/solved-using-client-subscribe-does-not-work-to-appsync-from-node/381/4) |
Thanks everyone for all the help here. That really helped. After some more research, I found this project. That did the trick for me |
Is anyone has made a npm package out of it? This would be really awesome 🙏 |
hi, i have the same problems
Hi bro, I have the same problem as you, can you tell me how to fix it, because I don't know how to use |
It would help you https://gist.github.com/wellitongervickas/087fb0d0550c429aae4500e4e4e9f624 library is not implement the payload data properly |
This is a great solution if you make requests using Api Key. But what if we need to use Cognito authorization to establish a connection ? Any ideas? |
@royroev if you still need help with using cognito authorization then this would help https://docs.amplify.aws/gen1/javascript/build-a-backend/graphqlapi/upgrade-guide/. use the import { fetchAuthSession } from 'aws-amplify/auth' and instead of apiKey, change to jwtToken and you can do something like this const auth = {
type: process.env.NEXT_PUBLIC_APPSYNCTYPE! as "AMAZON_COGNITO_USER_POOLS",
jwtToken: async () => {
try {
const authSession = await fetchAuthSession();
return authSession.tokens?.accessToken?.toString() || '';
} catch (error) {
console.error('Error fetching JWT token', error);
return '';
}
},
}; |
Recently, AWS AppSync published details of their websocket subscription workflow (https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html). It is now possible to implement a GraphQL client using only Apollo libraries, without the need for AppSync's own SDK. My team did this recently in order to side-step certain AppSync SDK bugs. I think it would be useful to others if this was available somewhere in Apollo's documentation. I provide it here for the Apollo team to disseminate if possible. There is no offline support in this example.
A number of customizations were required in order to make it work:
SubscriptionClient
fromsubscriptions-transport-ws
to use UUID operation IDs as this is recommended by AppSyncWebSocket
class to compute URL on instantiation sinceSubscriptionClient
has an invariant websocket URL butheader
query string parameter needs to stay in sync with JWT tokenWebSocket
class to filter out messages withdata.type === 'start_ack'
sinceSubscriptionClient
cannot handle this type of message sent by AppSyncSubscriptionClient
to retry the websocket connection on authorization failure. Eventually, a connection attempt will be made with a valid JWT token.SubscriptionClient
middleware to modify operations to include serialized GraphQL query and variables in adata
property and to add authorization information toextensions
Example usage
The text was updated successfully, but these errors were encountered: