onError Import not being used in Apollo - react-native

I'm trying to handle errors on my React Native with Expo app, while using Apollo and Graphql.
The problem is that in my apolloServices.js I'm trying to use onError function, and despite I tried with both apollo-link-error and #apollo/client/link/error the import is still greyed out. The function it's at the ApolloClient.
React-native: 0.63.2
#Apollo-client: 3.3.11
Apollo-link-error: 1.1.13
// Apollo resources
import { HttpLink } from 'apollo-link-http';
import { ApolloClient } from '#apollo/client'; // Tried putting onError here
import { onError } from 'apollo-link-error'; // And here
//import { onError } from '#apollo/client/link/error'; // Also here
import {
InMemoryCache,
defaultDataIdFromObject,
} from 'apollo-cache-inmemory';
import { setContext } from 'apollo-link-context';
/* Constants */
import { KEY_TOKEN } from '../constants/constants';
/**
* #name ApolloServices
* #description
* The Apollo Client's instance. App.js uses it to connect to graphql
* #constant httpLink HttpLink Object. The url to connect the graphql
* #constant defaultOPtions Object. Default options for connection
* #constant authLink
*/
const httpLink = new HttpLink({
uri: 'workingUri.com',
});
const defaultOptions = {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'ignore',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
};
//Create link with auth header
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
return AsyncStorage?.getItem(KEY_TOKEN).then((token) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
'auth-token': token ? token : '',
},
};
});
});
export default new ApolloClient({
/* ON ERROR FUNCTION HERE*/
onError: (graphQLErrors, networkError) => {
if (graphQLErrors)
graphQLErrors.map(
({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
},
cache: new InMemoryCache(),
defaultOptions: defaultOptions,
link: authLink.concat(httpLink),
request: (operation) => {
operation.setContext({
headers: {
'auth-token': token ? token : '',
},
});
},
});
Appart from this, the app is working perfectly, what I want it's just to handle graphql and network errors

The best solution would be to use this approach
const errorControl = onError(({ networkError, graphQLErrors }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
" [GraphQL error]: Message", message, ", Location: ", locations, ", Path: ", path)
);
}
if (networkError) {
console.log(" [Network error]:", networkError)
};
});
export default new ApolloClient({
cache: new InMemoryCache(),
defaultOptions: defaultOptions,
link: errorControl.concat(authLink.concat(httpLink)),
headers: {
authorization: token ? token : '',
},
});

Related

Failing to setup Websocket link

Been trying to get a Subscription working with Hasura and Vue Apollo with a websocket link with Vue Apollo with Vue3. Have it all seemingly setup.
The subscription works in Hasura so that’s right.
The query version worked with the HTTP link.
So the WS Link for some reason is just not working it. It seems like it might be authentication I’m not passing in correctly for some reason?
import './tailwind.css'
import App from './App.vue'
import { routes } from './routes.js'
import { createRouter, createWebHistory } from 'vue-router'
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client/core'
import { DefaultApolloClient } from '#vue/apollo-composable'
import { createAuth0 } from '#auth0/auth0-vue';
import { split } from 'apollo-link'
import { WebSocketLink } from 'apollo-link-ws'
import { getMainDefinition } from 'apollo-utilities'
import { HttpLink } from 'apollo-link-http'
const token = localStorage.getItem('Auth_token')
// HTTP connection to the API
const httpLink = new HttpLink({
// You should use an absolute URL here
uri: 'https://XXXXXXXXXXX.hasura.app/v1/graphql',
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": "XXXXXXXXXXX",
"Authorization": `Bearer ${token}`,
}
})
// Create the subscription websocket link
const wsLink = new WebSocketLink({
uri: 'ws://XXXXXXXXXXX.hasura.app/v1/graphql',
options: {
reconnect: true,
timeout: 30000,
inactivityTimeout: 30000,
lazy: true,
},
connectionParams: {
headers: {
"content-type": "application/json",
// "x-hasura-admin-secret": "XXXXXXXXXXX",
"Authorization": `Bearer ${token}`,
}
}
})
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
},
wsLink,
httpLink
)
// Cache implementation
const cache = new InMemoryCache()
// Create the apollo client
const apolloClient = new ApolloClient({
link,
cache,
connectToDevTools: true,
})
const app = createApp({
setup () {
provide(DefaultApolloClient, apolloClient)
},
render: () => h(App),
})
const router = createRouter({
history: createWebHistory(),
routes,
})
// router.beforeEach(async (to, from) => {
// console.log("it's here", this.$auth0)
// // if (
// // // make sure the user is authenticated
// // ) {
// // // redirect the user to the login page
// // }
// })
app.use(router)
app.use(
createAuth0({
domain: "XXXXXXXXXXX",
client_id: "JgajoigAywNqoIyvQWNJjpq6TS3g5Ljn",
// redirect_uri: "http://localhost:3000/the-clouds"
redirect_uri: window.location.origin
})
);
app.mount('#app')
Main.JS file
subscription working
subscription in vue apollo front end
error 1
error 2
Figured it out! Was headers setup wrong. Wooh!
// Create the subscription websocket link
const wsLink = new WebSocketLink({
uri: 'ws://XXXXX-backend.hasura.app/v1/graphql',
options: {
reconnect: true,
timeout: 30000,
inactivityTimeout: 30000,
lazy: true,
connectionParams: {
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": "XXXXX",
"Authorization": `Bearer ${token}`,
}
}
},
})

Getting Network request failed when uploading images with apollo client react native android

I am using ApolloClient to send mutation that contains files (images) but I am getting
Error: Network request failed
this what I have done to create links
import { createUploadLink } from 'apollo-upload-client' v ==>> "^15.0.0";
const uploadLink = createUploadLink({
uri: API_URL,
headers: {
"Authorization": `Bearer ${token}`,
'Content-Type': 'multipart/form-data',
"Accept":"application/json"
},
});
this to handle errors
import { onError } from "#apollo/client/link/error"; v ==>> "^3.3.20"
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network zaid error]: ${networkError}`);
});
then :
const client = new ApolloClient({
cache: new InMemoryCache(),
link: from([errorLink,uploadLink]),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
errorPolicy: 'none'
},
mutate: {
mutation: "DocumentNode",
errorPolicy: 'none'
},
},
});
then I called the mutation :
await client.mutate({
mutation:
gql`
mutation($data: PostCreatInput!, $files: [CustomCreateImages!]!) {
createpost(data: $data, files: $files) {
id
}}`,
variables: {
data: {****},
files:[{file:ReactNativeFile}]
}
}).then(response => {
console.log(response);
return response
}).catch(response => {
console.log(response);
return response
})
i used ReactNativeFile generated by apollo-upload-client
new ReactNativeFile({
uri: "file:///storage/***.jpg",
name: "a.jpg",
type: "image/jpeg"
});
I am using react native "react-native": "0.62.2"
and I have a live server not using localhost
I did check the server logs this request never left the mobile; there was no record of it on the server.
been stuck all day on it, any help would be highly appreciated!
bug with React Native 0.62+ that messes up the configuration for multiform requests. It can be fixed by commenting out line 43 in android/app/src/debug/java/com/maxyride/app/drivers/ReactNativeFlipper.java:
//builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));

NextJs/ Apollo Client/ NextAuth issue setting authorization Bearer Token to headers correctly

I cannot correctly set my jwt token from my cookie to my Headers for an authenticaed gql request using apollo client.
I believe the problem is on my withApollo.js file, the one that wraps the App component on _app.js. The format of this file is based off of the wes bos advanced react nextjs graphql course. What happens is that nextauth saves the JWT as a cookie, and I can then grab the JWT from that cookie using a custom regex function. Then I try to set this token value to the authorization bearer header. The problem is that on the first load of a page with a gql query needing a jwt token, I get the error "Cannot read property 'cookie' of undefined". But, if I hit browser refresh, then suddenly it works and the token was successfully set to the header.
Some research led me to adding a setcontext link and so that's where I try to perform this operation. I tried to async await setting the token value but that doesn't seem to have helped. It just seems like the headers don't want to get set until on the refresh.
lib/withData.js
import { ApolloClient, ApolloLink, InMemoryCache } from '#apollo/client';
import { onError } from '#apollo/link-error';
import { getDataFromTree } from '#apollo/react-ssr';
import { createUploadLink } from 'apollo-upload-client';
import withApollo from 'next-with-apollo';
import { setContext } from 'apollo-link-context';
import { endpoint, prodEndpoint } from '../config';
import paginationField from './paginationField';
const getCookieValue = (name, cookie) =>
cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`)?.pop() || '';
let token;
function createClient(props) {
const { initialState, headers, ctx } = props;
console.log({ headers });
// console.log({ ctx });
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}),
setContext(async (request, previousContext) => {
token = await getCookieValue('token', headers.cookie);
return {
headers: {
authorization: token ? `Bearer ${token}` : '',
},
};
}),
createUploadLink({
uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
fetchOptions: {
credentials: 'include',
},
headers,
}),
]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// TODO: We will add this together!
// allProducts: paginationField(),
},
},
},
}).restore(initialState || {}),
});
}
export default withApollo(createClient, { getDataFromTree });
page/_app.js
import { ApolloProvider } from '#apollo/client';
import NProgress from 'nprogress';
import Router from 'next/router';
import { Provider, getSession } from 'next-auth/client';
import { CookiesProvider } from 'react-cookie';
import nookies, { parseCookies } from 'nookies';
import Page from '../components/Page';
import '../components/styles/nprogress.css';
import withData from '../lib/withData';
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
function MyApp({ Component, pageProps, apollo, user }) {
return (
<Provider session={pageProps.session}>
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} {...user} />
</Page>
</ApolloProvider>
</Provider>
);
}
MyApp.getInitialProps = async function ({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
pageProps.query = ctx.query;
const user = {};
const { req } = ctx;
const session = await getSession({ req });
if (session) {
user.email = session.user.email;
user.id = session.user.id;
user.isUser = !!session;
// Set
nookies.set(ctx, 'token', session.accessToken, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
}
return {
pageProps,
user: user || null,
};
};
export default withData(MyApp);
api/auth/[...nextAuth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from 'axios';
const providers = [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
Providers.Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await axios
.post('http://localhost:1337/auth/local', {
identifier: credentials.username,
password: credentials.password,
})
.then((res) => {
res.data.user.token = res.data.jwt;
return res.data.user;
}) // define user as res.data.user (will be referenced in callbacks)
.catch((error) => {
console.log('An error occurred:', error);
});
if (user) {
return user;
}
return null;
},
}),
];
const callbacks = {
// Getting the JWT token from API response
async jwt(token, user, account, profile, isNewUser) {
// WRITE TO TOKEN (from above sources)
if (user) {
const provider = account.provider || user.provider || null;
let response;
let data;
switch (provider) {
case 'google':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/google/callback?access_token=${account?.accessToken}`
);
data = await response.json();
if (data) {
token.accessToken = data.jwt;
token.id = data.user._id;
} else {
console.log('ERROR No data');
}
break;
case 'local':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/local/callback?access_token=${account?.accessToken}`
);
data = await response.json();
token.accessToken = user.token;
token.id = user.id;
break;
default:
console.log(`ERROR: Provider value is ${provider}`);
break;
}
}
return token;
},
async session(session, token) {
// WRITE TO SESSION (from token)
// console.log(token);
session.accessToken = token.accessToken;
session.user.id = token.id;
return session;
},
redirect: async (url, baseUrl) => baseUrl,
};
const sessionPreferences = {
session: {
jwt: true,
},
};
const options = {
providers,
callbacks,
sessionPreferences,
};
export default (req, res) => NextAuth(req, res, options);

How can i use GraphQl subscriptions in react-native chat application to get real-time updates from GraphQl queries

I am using GraphQl APIs in the react-native chat application. I want to get real-time updates when another user sends a message to me without refreshing the API. How can I do it using GraphQl API using GraphQl subscriptions or Websocket in react-native?
Should I use different URLs for subscription and normal API's?
Here is my config.js
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { HttpLink } from 'apollo-boost';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { AsyncStorage } from 'react-native';
// const httpLink = createHttpLink({
// uri: 'https://graphql.chat.dev.com/graphql',
// });
// const link = new HttpLink({
// uri: `https://graphql.chat.dev.com/graphql`,
// headers: {
// Authorization: AsyncStorage.getItem('#user_token');
// }
// });
const link = new WebSocketLink({
uri: `wss://graphql.chat.dev.com/graphql`,
options: {
reconnect: true,
connectionParams: {
headers: {
Authorization: AsyncStorage.getItem('#user_token');
}
}
}
})
const defaultOptions = {
query: {
fetchPolicy: "network-only",
errorPolicy: "all"
}
};
const client = new ApolloClient({
link: link,
cache: new InMemoryCache(),
defaultOptions
});
export default client;
I've not implemented Apollo with React Native but I did it with my React app. In my experience, you should use different URLs for subscription and normal APIs. Then, use import { split } from 'apollo-link' to split links, so you can send data to each link
depending on what kind of operation is being sent. You can read more about subscription in Apollo here.
This is my client.js file. Hopefully, it can help you.
import { ApolloClient } from 'apollo-client'
import { createUploadLink } from 'apollo-upload-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { setContext } from 'apollo-link-context'
import { split } from 'apollo-link'
import { WebSocketLink } from 'apollo-link-ws'
import { getMainDefinition } from 'apollo-utilities'
const getToken = () => localStorage.getItem('AUTH_TOKEN')
const APOLLO_SERVER ="APOLLO_SERVER url"
const APOLLO_SOCKET ="APOLLO_SOCKET url"
// Create an http link:
const httpLink = createUploadLink({
uri: APOLLO_SERVER,
credentials: 'same-origin',
})
const authLink = setContext((_, { headers }) => {
const token = getToken()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
}
})
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: APOLLO_SOCKET,
options: {
reconnect: true,
connectionParams: {
Authorization: getToken() ? `Bearer ${getToken()}` : '',
},
},
})
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
authLink.concat(httpLink)
)
const cache = new InMemoryCache()
const client = new ApolloClient({
cache,
link,
typeDefs,
resolvers,
})
This is my component where I integrate queries with subscriptions:
import React, { useEffect } from 'react'
import { useQuery } from '#apollo/react-hooks'
import gql from 'graphql-tag'
...
// query for querying message list
const GET_MESSAGE_LIST = gql`...`
// subscription for listening new message
const ON_MESSAGE_CREATED = gql`...`
const ChatView = props => {
const { data, loading, subscribeToMore } = useQuery(GET_MESSAGE_LIST, {
{
notifyOnNetworkStatusChange: true,
variables: {
query: {
limit: 10,
userId: props.userId,
},
},
}
})
useEffect(() => {
subscribeToMore({
document: ON_MESSAGE_CREATED,
variables: { filter: { userId: props.userId } },
shouldResubscribe: true,
updateQuery: (prev, { subscriptionData }) => {
let newMessage = subscriptionData.data.onZaloMessageCreated
return Object.assign({}, prev, {
messageList: {
...prev.messageList,
items:
prev.messageList.items.filter(
item => item.id === newMessage.id
).length === 0
? [newMessage, ...prev.messageList.items]
: prev.messageList.items,
},
})
},
})
}, [subscribeToMore])
return ...
}

All apollo query errors throw a network 400 error

How do I globally handle my query and mutation errors? When I use a malformed graphql query, in a mutation or query, my react-native app always throws the following error.
Unhandled (in react-apollo:Apollo(MyScreen)) Error: Network error:
Response not successful: Received status code 400
Running the same queries on the graphiql endpoint, provide relevant errors however. While the goal is globally handled errors, local query errors also do not work for me. Printing them shows nothing.
//MyComponent.js
class MyComponent extends React.Component {
render () {
/*
outputs
{
error: undefined
loading: true
networkStatus: 1
}
*/
console.log(this.props.data);
//outputs undefined
console.log('error', this.props.error);
//outputs undefined
console.log('errors', this.props.errors);
return (
<Container>
{this.props.myData}
<Button block onPress={this.props.onPress}>
<Text>Button</Text>
</Button>
</Container>
)
}
}
const mapDispatchToProps = (dispatch, {navigation: {navigate}}) => ({
onPress: async rest => {
//do stuff
}
});
export default connect(null, mapDispatchToProps)(MyDataService.getMyDataInjector(MyComponent));
//MyDataService.js
import { graphql } from 'react-apollo';
import getApolloClient from '../../../apolloClient';
import { dataFragment } from './dataFragments';
import gql from 'graphql-tag';
export default MyDataService = {
getMyDataInjector: graphql(gql`
query {
myData {
...dataFragment
}
}
${dataFragment}
`,
{
props: ({ data, errors, error }) => ({
data,
loading: data.loading,
myData: data.myData,
errors,
error,
})
}),
addData: async (data) => {
const res = await apolloClient.mutate({
mutation: gql`
mutation addData($data: String!) {
addData(data: $data) {
...dataFragment
}
}
${dataFragment}
`,
variables: {
data,
}
});
return res.data.addData;
},
};
//apolloClient.js
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';
let apolloClient;
const initApolloClient = store => {
const httpLink = new HttpLink({uri: 'http://192.168.0.11:3000/graphql'})
// const errorLink = onError(({ response, graphQLErrors, networkError }) => {
// console.log('graphql error in link', graphQLErrors);
// console.log('networkError error in link', networkError);
// console.log('response error in link', response);
// if (graphQLErrors)
// graphQLErrors.map(({ message, locations, path }) =>
// console.log(
// `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
// ),
// );
// if (networkError) console.log(`[Network error]: ${networkError}`);
// });
const authLink = setContext((_, { headers }) => {
const {accessToken: {type, token}} = store.getState().signedInUser;
return {
headers: {
...headers,
authorization: type && token ? type + ' ' + token : null
}
}
});
apolloClient = new ApolloClient({
// using error link throws - Error: Network error: forward is not a function
// link: authLink.concat(errorLink, httpLink),
link: authLink.concat(httpLink),
// doesn't console log
// onError: (e) => { console.log('IN ON ERROR', e.graphQLErrors) },
cache: new InMemoryCache({
dataIdFromObject: o => (o._id ? `${o.__typename}:${o._id}`: null),
}),
// turning these on do nothing
// defaultOptions: {
// query: {
// // fetchPolicy: 'network-only',
// errorPolicy: 'all',
// },
// mutate: {
// errorPolicy: 'all'
// }
// }
});
return apolloClient;
}
export default getApolloClient = () => apolloClient;
export { initApolloClient };
was able to get it working by using apollo-link with onError.
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';
let apolloClient;
const initApolloClient = store => {
const httpLink = new HttpLink({uri: 'http://192.168.0.11:3000/graphql'})
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.error(
`[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(locations)}, Path: ${path}`,
),
);
}
if (networkError) console.error(`[Network error]: ${networkError}`);
});
const authLink = setContext((_, { headers }) => {
const {accessToken: {type, token}} = store.getState().signedInUser;
return {
headers: {
...headers,
authorization: type && token ? type + ' ' + token : null
}
}
});
const link = ApolloLink.from([
authLink,
errorLink,
httpLink,
]);
apolloClient = new ApolloClient({
link,
cache: new InMemoryCache({
//why? see https://stackoverflow.com/questions/48840223/apollo-duplicates-first-result-to-every-node-in-array-of-edges/49249163#49249163
dataIdFromObject: o => (o._id ? `${o.__typename}:${o._id}`: null),
}),
});
return apolloClient;
}