How to use Apollo Client with AppSync? - websocket

AppSync uses MQTT over WebSockets for its subscription, yet Apollo uses WebSockets. Neither Subscription component or subscribeForMore in Query component works for me when using apollo with AppSync.
One AppSync feature that generated a lot of buzz is its emphasis on
real-time data. Under the hood, AppSync’s real-time feature is powered
by GraphQL subscriptions. While Apollo bases its subscriptions on
WebSockets via subscriptions-transport-ws, subscriptions in GraphQL
are actually flexible enough for you to base them on another messaging
technology. Instead of WebSockets, AppSync’s subscriptions use MQTT as
the transport layer.
Is there any way to make use of AppSync while still using Apollo?

Ok, here is how it worked for me. You'll need to use aws-appsync SDK (https://github.com/awslabs/aws-mobile-appsync-sdk-js) to use Apollo with AppSync. Didn't have to make any other change to make subscription work with AppSync.
Configure ApolloProvider and client:
// App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font, Icon } from 'expo';
import AWSAppSyncClient from 'aws-appsync' // <--------- use this instead of Apollo Client
import {ApolloProvider} from 'react-apollo'
import { Rehydrated } from 'aws-appsync-react' // <--------- Rehydrated is required to work with Apollo
import config from './aws-exports'
import { SERVER_ENDPOINT, CHAIN_ID } from 'react-native-dotenv'
import AppNavigator from './navigation/AppNavigator';
const client = new AWSAppSyncClient({
url: config.aws_appsync_graphqlEndpoint,
region: config.aws_appsync_region,
auth: {
type: config.aws_appsync_authenticationType,
apiKey: config.aws_appsync_apiKey,
// jwtToken: async () => token, // Required when you use Cognito UserPools OR OpenID Connect. token object is obtained previously
}
})
export default class App extends React.Component {
render() {
return <ApolloProvider client={client}>
<Rehydrated>
<View style={styles.container}>
<AppNavigator />
</View>
</Rehydrated>
</ApolloProvider>
}
Here is how the subscription in a component looks like:
<Subscription subscription={gql(onCreateBlog)}>
{({data, loading})=>{
return <Text>New Item: {JSON.stringify(data)}</Text>
}}
</Subscription>

Just to add a note about the authentication as it took me a while to work this out:
If the authenticationType is "API_KEY" then you have to pass the apiKey as shown in #C.Lee's answer.
auth: {
type: config.aws_appsync_authenticationType,
apiKey: config.aws_appsync_apiKey,
}
If the authenticationType is "AMAZON_COGNITO_USER_POOLS" then you need the jwkToken, and
if you're using Amplify you can do this as
auth: {
type: config.aws_appsync_authenticationType,
jwtToken: async () => {
const session = await Auth.currentSession();
return session.getIdToken().getJwtToken();
}
}
But if your authenticationType is "AWS_IAM" then you need the following:
auth: {
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials()
}

Related

Amplify PubSub javascript subscribe and publish using cognito authorization: how to?

i want a cognito authorized user to be able to publish and subscribe to AWS IoT using Amplify PubSub but am having difficulty doing so.
i use javascript (inside a Vue app)
i have successfully managed to publish and subscribe to AWS IoT using the aws-iot-device-sdk so all the policies, etc are working.
here's the code i'm using with PubSub:
const region = "eu-west-1";
const endpoint = "blahBlah-ats.iot.eu-west-1.amazonaws.com";
const currentlySubscribedTopic = "myTopic";
Amplify.addPluggable(
new AWSIoTProvider({
aws_pubsub_region: region,
// aws_pubsub_endpoint: "xxxxxxzbx-ats.iot.eu-west-1.amazonaws.com"
// aws_pubsub_endpoint: 'wss://xxxxxxxxxxxxx.iot.<YOUR-IOT-REGION>.amazonaws.com/mqtt',
aws_pubsub_endpoint: "wss://" + endpoint + "/mqtt"
})
);
to subscribe:
PubSub.subscribe(currentlySubscribedTopic).subscribe({
next: data => console.log("Message received", data),
error: error => console.error(error),
close: () => console.log("Done")
});
to publish I call this method:
async publishMessage() {
console.log("publishing message...");
await PubSub.publish(currentlySubscribedTopic, {
msg: "Hello to all subscribers!"
});
console.log("published..");
}
unfortunately nothing happens (not even errors), so I am obviously doing something wrong.
how do i actually make the connection? is there a connect() function?
how do i incorporate the cognito credentials? i have tried this inside Auth.currentSession():
AWS.config.update({
region: "eu-west-1",
credentials: new AWS.CognitoIdentityCredentials({
IdentityPoolId: "eu-west-1:xxxx-7be8-487a-9e0b-91980xxx976e",
Logins: {
// optional tokens, used for authenticated login
"cognito-idp.eu-west-1.amazonaws.com/eu-west-1_xxxx4OFmI":
session.idToken.jwtToken
}
})
});
and can use AWS.config.credentials.get() to get the credentials but don't know how to use them to make the connection.
i'd appreciate some help as to how to do this.
thanks
I was toiling with this for quite awhile today and finally got it working. Pasting my code below. Though its not shown, the PubSub.publish() also worked fine too.
# App.js
import React from 'react';
import './App.css';
import Amplify from 'aws-amplify';
import PubSub from '#aws-amplify/pubsub';
import { AWSIoTProvider } from '#aws-amplify/pubsub/lib/Providers';
import awsconfig from './aws-exports';
import { withAuthenticator, AmplifySignOut } from '#aws-amplify/ui-react';
import EventViewer from './event-viewer.js';
Amplify.Logger.LOG_LEVEL = 'VERBOSE';
Amplify.addPluggable(new AWSIoTProvider({
aws_pubsub_region: 'us-west-2',
aws_pubsub_endpoint: 'wss://MY_IOT_ENDPOINT-ats.iot.us-west-2.amazonaws.com/mqtt',
}));
Amplify.configure(awsconfig);
PubSub.configure();
PubSub.subscribe('myTopic1').subscribe({
next: data => console.log('Message received', data),
error: error => console.error(error),
close: () => console.log('Done'),
});
function App() {
return (
<div className="App">
<AmplifySignOut />
<EventViewer/>
</div>
);
}
export default withAuthenticator(App);
Keep in mind, this also requires that:
You have created a proper AWS IoT policy (e.g. for example, having iot:Connect, and having iot:Subscribe and iot:Receive scoped to the appropriate topics)
You have attached the IoT Policy to your user's Cognito Federated Identity Id

"Must provide query string" error with Apollo Client on React and Nexus Graphql Server

I'm starting to work with GraphQL and the new Nexus Framework GraphQL server, which is a great product.
On my server-side, I defined my schema, I can query my database with Prisma and everything runs smoothly. I can query data also from the Nexus GraphQL playground and also with Postman.
Now, I want to make things work on the client-side. I see that Apollo Client is the best solution to integrate React with GraphQL, but I just can't make things work. I read tons of docs but I'm missing something that I can't figure out.
GraphQL and the client part will be hosted on the same server, on separate node applications.
I'm configuring Apollo based on its documentations. The example below is for the new 3.0 Beta Version of Apollo which I'm testing, but the same scenario happens on the last stable version. I believe that I need to do something else to integrate Apollo and Nexus.
Every query returns: "Must Provide Query String".
The same query inside the playground works perfectly.
Here is my basic testing code:
apollo.js:
import { ApolloClient, HttpLink, InMemoryCache } from '#apollo/client'
const client = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: 'http://localhost:4000/graphql',
fetchOptions: {
mode: 'no-cors',
}
})
})
export default client
App.js:
import React from 'react'
import { ApolloProvider } from '#apollo/client';
import client from './database/apollo'
import Home from './components/Home'
const App = () => {
return (
<ApolloProvider client={client}>
<Home />
</ApolloProvider>
)
}
export default App;
Home.js:
import React, { useState, useEffect, useReducer } from 'react'
import { useQuery, gql } from '#apollo/client'
const PUBLICATIONS = gql`
{
albumreviews(last: 1) {
title
}
}
`
const Home = () =>{
const { loading, error, data } = useQuery(PUBLICATIONS)
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
return data.albumreviews.map(({ review }) => (
<div>{JSON.parse(review)}</div>
))
}
export default Home
On the client-side: "Error" is displayed.
On the server-side: "Must provide query string"
Believe me, I've tried to adjust the query thousands of times trying to get a different answer.
Could some help me to move forward with this? Should I provide the Nexus schema to the apollo client? What is the better way of doing this?
You should pretty much never use no-cors. Off hand, I'm not sure why that option would cause your request to be malformed, but it will make it impossible for your response to be read anyway. Remove fetchOptions and whitelist your client URL in your CORS configuration on the server-side. CORs usage with Nexus is shown here in the docs.

Integrate Bot with Direct Line API

I have a bot developed in SDK 4 and deployed in IIS. When I integrate with web chat using secret it works .
window.WebChat.renderWebChat(
{
directLine: window.WebChat.createDirectLine({
secret: 'SECRET CODE'
}),
// Passing 'styleOptions' when rendering Web Chat
styleOptions
},
document.getElementById('webchat')
);
But it have very limited styling options in above case. I would like to use REACT which requires token exchange. I am not sure how this will be used? Mean what changes are required at client end & what at Bot end? I cannot find any descriptive document for this. Would be great if we can sample for both at client & Bot end changes for token exchange.
It is possible to use REACT to style your webchat. The botframework-webchat repo here has samples and examples displaying this. For the token exchange, you're going to want to use the following:
import { DirectLine } from 'botframework-directlinejs';
import React from 'react';
import ReactWebChat from 'botframework-webchat';
export default class extends React.Component {
constructor(props) {
super(props);
this.directLine = new DirectLine({ token: 'YOUR_DIRECT_LINE_TOKEN' });
}
render() {
return (
<ReactWebChat directLine={ this.directLine } userID='YOUR_USER_ID' />
element
);
}
}

GraphQL react-apollo - is it okay to export/import client directly?

I have an apollo setup file, services/apollo.js, where I export the client:
const client = new ApolloClient({
cache,
link: ApolloLink.from([stateLink, httpLink])
})
export default client
and I then import that and use it as normal elsewhere:
<BrowserRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</BrowserRouter>
If I want to use the client directly (for manually firing off a query for example), is it okay to import it directly into the file, rather than accessing it via ApolloConsumer?
import client from 'services/apollo'
export const getSomeData = async () => {
const { data } = await client.query({ ... })
console.log(data)
}
Yup, that should be fine.
This blog post mentions using a singleton for Apollo:
I use a singleton with a unique apollo client to keep a unique cache
and use in all code.
https://cheesecakelabs.com/blog/apollo-graphql-client-makes-api-integration-breeze/

How to call an apollo client query from a redux action

If I'm using redux and the apollo client in my app, what's the best way to trigger a query from an action outside of a component.
For example, if I have a standard app, with redux and apollo client configured, how should I trigger a "refresh" list. I can trigger a function on the component itself which has the gql, but how would I do it from an action which would be more in line with flux.
import React, { Component, PropTypes } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { connect } from 'react-redux';
import { refreshProfile } from './actions';
class Profile extends Component { ... }
Profile.propTypes = {
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
user: PropTypes.object,
}).isRequired,
};
const UserQuery = gql`
query getUser {
user {
id
name
}
}
`;
const ProfileWithData = graphql(UserQuery)(Profile);
const ProfileWithDataAndState = connect(
(state) => ({ user: state.user })),
)(ProfileWithData);
And, say I want to trigger an action to refresh that user data? Since the logic is in the component itself, I'm not sure how I would trigger that gql query from the action itself.
I would need to use the ApolloClient in my actions.js. e.g.
import ApolloClient, { createNetworkInterface } from 'apollo-client';
const networkInterface = createNetworkInterface({
uri: config.graphCoolUri,
});
const client = new ApolloClient({
networkInterface,
dataIdFromObject: r => r.id,
});
const { data } = await client.query({
query: UserQuery
});
I see your needs, as I was just in your place couple of days ago.
The sad news is: if you want to use actions with graphQL, then you shouldn't be using apollo, just use graphQL directly. This is a very good article to walk you through - getting started with Redux and GraphQL. Why? Because Apollo uses a function called qraphql(query) which calls its own action.
How both Redux and Apollo work in a very simplistic way.
Redux: (User dispatches an action) ActionCreator --> Action --> Middleware --> reducer --> store --> bind data to user props. And we control each state manually.
Apollo: (User passes the query/mutation to graphql(query)) all hidden (action --> store) then binds data to user props.
You can say that Apollo replaces Redux if you are using graphql, because it has a better integration with react and graphQL.
In the meantime, as Apollo is still developing, you might need redux for redux-form and so on. If you are used to some redux libraries, which you might consider to continue using redux besides Apollo, you can still bind their stores and add costumed middleware that probably apply to both, but you probably won't be fetching data using Redux actions through Apollo.
I know it feels like you are loosing redux, but you are getting all advantages with more async requests and caching taking care of with Apollo.
and if you need a place to start react-redux-apollo.

Resources