Handling errors for Apollo Client when using ApolloLink.split - javascript

I have a simple code:
import { split } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws'
import { HttpLink } from 'apollo-link-http'
import ApolloClient from 'apollo-client'
import { onError } from 'apollo-link-error'
const wsLink = new WebSocketLink({
uri: hasura.wsUrl,
options: {
reconnect: true,
timeout: 30000,
connectionParams: {
headers: {
'Authorization': `Bearer ${this.token}`
}
}
}
})
const httpLink = new HttpLink({
uri: hasura.httpUrl,
headers: {
'Authorization': `Bearer ${this.token}`
}
})
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink
)
const errorLink = onError(({graphQLErrors, networkError}) => {
// this callback is never called
console.log('graphQLErrors', graphQLErrors)
console.log('networkError', networkError)
})
this.client = new ApolloClient({
link: errorLink.concat(link),
cache: new InMemoryCache()
})
How I can a handling errors for the "split" links? For this example catching errors doesn't works. If I use links without "split" function errors catching works.

let link = ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
}
if (networkError) console.error(`[Network error]: ${networkError}`, networkError.stack);
}),
ApolloLink.split(
operation => operation.getContext().important === true,
httpLink, // don't batch important
batchHttpLink
),
]);

Related

WebSocket connection to 'ws://localhost:4000/graphql' failed:

I am getting this Websocket failed to Connect error for both client and server side now (as shown in the image below). I am not using any other Websocket configuration other than the one specified in the apollo client. This has been baffling me for about 2 days. Any help would be appreciated. Let me know if you need to see any further code.
I have a Vue app client that connects to graphql apollo server. The code for apolloclient configuration is given below.
// Apollo packages
import { ApolloClient } from "apollo-boost-upload";
import { WebSocketLink } from "apollo-link-ws";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { split } from "apollo-link";
import { getMainDefinition } from "apollo-utilities";
import VueApollo from "vue-apollo";
Vue.use(VueApollo);
wsLink = new WebSocketLink({
uri: "ws://localhost:4000/graphql", // use wss for a secure endpoint
options: {
reconnect: true,
},
});
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
httpLink
);
// Cache implementation
export const defaultClient = new ApolloClient({
// uri: "http://localhost:4000/graphql",
link,
cache: new InMemoryCache(),
fetchOptions: {
credentials: "include",
},
request: (operation) => {
// if no token in local storage, add it
if (!localStorage.anaceptToken) {
localStorage.setItem("anaceptToken", "");
}
// operation adds the token to authorizatrion header, which is sent o backend
operation.setContext({
headers: {
authorization: "Bearer " + localStorage.getItem("anaceptToken"),
},
});
},
onError: ({ graphQLErrors, networkError }) => {
if (networkError) {
console.log("[networkError]", networkError);
}
if (graphQLErrors) {
for (const error of graphQLErrors) {
console.dir(error);
console.log(error);
if (
error.name === "AuthenticationError" ||
error.message === "jwt expired"
) {
// set auth error in state
store.commit("setError", error);
// signout user to clear error
store.dispatch("signUserOut");
}
}
}
},
});
vue config file
const { defineConfig } = require("#vue/cli-service");
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
pluginOptions: {
apollo: {
enableMocks: true,
enableEngine: true,
},
},
transpileDependencies: ["vuetify"],
chainWebpack: (config) => {
config.performance.maxEntrypointSize(400000).maxAssetSize(400000);
new NodePolyfillPlugin();
},
});
interesting try localhost 4004, it should work

Apollo Websocket - Unable to initiate with new connection with latest JWT token

I have a vue application that connects to a graphQL server using vue apollo. The vue-apollo.js code is as follows
const httpEndpoint = process.env.XXX;
const wsEndpoint = process.env.XXX;
let baseLink = new ApolloLink((operation, forward) => {
let isTokenExpired = isJwtExpired(token)
console.log('isExpired is:',isTokenExpired) ;
if (isTokenExpired) {
Auth.currentSession().then((data) => {
token = data.idToken.jwtToken
localStorage.setItem('JoT', JSON.stringify(token));
const headerData = token? {authorization: `Bearer ${token}`,}: {};
operation.setContext({headers: headerData})
return forward(operation)
})
} else {
token = JSON.parse(token);
const headerData = token? {authorization: `Bearer ${token}`,}: {};
operation.setContext({headers: headerData})
return forward(operation)
}
})
let observerLink = new ApolloLink((operation, forward) => {
return forward(operation)
})
const baseAndObserverLink = baseLink.concat(observerLink)
const errorLink = onError(error => {
console.log(JSON.stringify(error))
if (error.graphQLErrors) {
console.log("GraphQL Error detected")
if (error.graphQLErrors.extensions && error.graphQLErrors.extensions.code && error.graphQLErrors.extensions.code === 'invalid-jwt') {
console.log("JWT EXPIRED")
}
} else if (error.networkError && error.networkError.extensions && error.networkError.extensions.code && error.networkError.extensions.code === 'start-failed') {
console.log("GraphQL Error detected type 2")
console.log("Unable to Connect")
}else if (error.networkError && error.networkError.extensions && error.networkError.extensions.code && error.networkError.extensions.code === 'validation-failed') {
console.log("GraphQL Error detected type 3")
console.log("Validation Error")
}
else {
console.log(JSON.stringify(error))
}
})
var wsLink = new WebSocketLink({
uri: wsEndpoint,
options: {
reconnect: true,
lazy: true,
timeout: 30000,
connectionParams: async () => {
const token = JSON.parse(localStorage.getItem('JWT'));
return {
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
}
},
},
});
export var filesRoot =
process.env.VUE_APP_FILES_ROOT ||
httpEndpoint.substr(0, httpEndpoint.indexOf('/graphql'));
Vue.prototype.$filesRoot = filesRoot;
var httpLink = new HttpLink({
uri: httpEndpoint,
});
var splitLLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
export var defaultOptions = {
httpEndpoint,
wsEndpoint,
tokenName: 'JWT',
persisting: false,
websocketsOnly: true,
ssr: false,
link:baseAndObserverLink.concat(errorLink).concat(splitLLink),
};
export function createProvider(options = {}) {
const { apolloClient, wsClient } = createApolloClient({
...defaultOptions,
...options,
});
apolloClient.wsClient = wsClient;
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
defaultOptions: {
$query: {
// fetchPolicy: 'cache-and-network',
},
},
async errorHandler(error) {
},
});
return apolloProvider;
}
This code when we first login works well. When I refetch a new token the query part gets updated with new context and everything works fine. However the websocket connection seems to keep using the old token to reinitialise connection. So far I couldn't find any suitable solution except for do a forced reload which reinitilises the entire app and wss conection can work as we have latest token being passed. Is there another way

Add the Apollo React onError from apollo-link-error to the link const

I want to add onError to my index.js Apollo file. So that video helped me how a very basic example looks like. But as I have some more links in my project, it's a bit different to what is shown there.
Index.js:
import { InMemoryCache } from 'apollo-cache-inmemory'
import { setContext } from 'apollo-link-context'
import { WebSocketLink } from 'apollo-link-ws'
import { split } from 'apollo-link'
import { onError } from "apollo-link-error";
const httpLink = createHttpLink({
uri: 'http://localhost:4000',
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(AUTH_TOKEN)
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
}
})
const wsLink = new WebSocketLink({
uri: `ws://localhost:4000`,
options: {
reconnect: true,
connectionParams: {
authToken: localStorage.getItem(AUTH_TOKEN),
},
},
})
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
authLink.concat(httpLink),
)
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
})
Now I want to add the errorLink to my project to track error with this code:
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, location, path }) =>
console.log(`[GraphQL error]: Message: ${message}, Location: ${location}, Path: ${path}`),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
But I'm not sure how to add that new link to the link const. Is it done with a concat or something else?
I already had a look on the composing links section. But that's also too different from my example.
split is returning a new ApolloLink.
In this case, doing link: ApolloLink.from([errorLink, link]) should work. It will create a new ApolloLink from an array of ApolloLink.
As I also wanted to not let the user run into an error message. This is one method that prevents the frontend from showing the error:
const defaultOptions = {
query: {
errorPolicy: 'all',
},
mutate: {
errorPolicy: 'all'
}
}
const client = new ApolloClient({
link: ApolloLink.from([errorLink, link]),
cache: new InMemoryCache(),
defaultOptions,
})

How to store, manage REST API JWT authentication token in vue?

I am a noob, using vue.js and a node auth api, the api works fine and provides the jwt token in the response, my question is how can i use the token in all the requests that follows (using axios), and any best practices for handling the token in the front end is also appreciated.
Thanks
You can use something like that for Your scenario in your vuejs app.
import axios from 'axios'
const API_URL = 'http://localhost:3000'
const securedAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
const plainAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
securedAxiosInstance.interceptors.request.use(config => {
const method = config.method.toUpperCase()
if (method !== 'OPTIONS' && method !== 'GET') {
config.headers = {
...config.headers,
'X-CSRF-TOKEN': localStorage.csrf
}
}
return config
})
securedAxiosInstance.interceptors.response.use(null, error => {
if (
error.response &&
error.response.config &&
error.response.status === 401
) {
return plainAxiosInstance
.post('/refresh', {}, { headers: { 'X-CSRF-TOKEN': localStorage.csrf } })
.then(response => {
localStorage.csrf = response.data.csrf
localStorage.signedIn = true
let retryConfig = error.response.config
retryConfig.headers['X-CSRF-TOKEN'] = localStorage.csrf
return plainAxiosInstance.request(retryConfig)
})
.catch(error => {
delete localStorage.csrf
delete localStorage.signedIn
location.replace('/')
return Promise.reject(error)
})
} else {
return Promise.reject(error)
}
})
export { securedAxiosInstance, plainAxiosInstance }
And in your component you use this to process your request with api
Products.vue
export default {
name: 'products',
data () {
return {
products: [],
newProduct: [],
error: '',
editedProduct: ''
}
},
created () {
if (!localStorage.signedIn) {
this.$router.replace('/')
} else {
this.$http.secured.get('/api/v1/products')
.then(response => { this.products = response.data })
.catch(error => this.setError(error, 'Something went wrong'))
}
},
methods: {
setError (error, text) {
this.error = (error.response && error.response.data && error.response.data.error) || text
},
addProduct () {
const value = this.newProduct
if (!value) {
return
}
this.$http.secured.post('/api/v1/products/', { product: { name: this.newProduct.name } })
.then(response => {
this.products.push(response.data)
this.newProduct = ''
})
.catch(error => this.setError(error, 'Cannot create product'))
},
removeProduct (product) {
this.$http.secured.delete(`/api/v1/products/${product.id}`)
.then(response => {
this.products.splice(this.products.indexOf(product), 1)
})
.catch(error => this.setError(error, 'Cannot delete product'))
},
editProduct (product) {
this.editedproduct = product
},
updateProduct (product) {
this.editedProduct = ''
this.$http.secured.patch(`/api/v1/products/${product.id}`, { product: { title: product.name } })
.catch(error => this.setError(error, 'Cannot update product'))
}
}
}
You can find here a lot of good patterns which I personally use on my projects and how also JWT token handling.
For saving token in a brower, you can use cookie, sessionStorage or localStorate, last one is the most popular now (short explination here).
In a few words, you can create an axion instance and add a token before request sent.
const http = axios.create({
baseURL: process.env.VUE_APP_SERVER_API,
// here you can specify other params
})
http.interceptors.request.use(request => {
// Do something before request is sent
request.headers['Authorization'] = `JWT ${TOKEN_HERE}`
// some logic what to do if toke invalid, etc ...
return request
}, function (error) {
// Do something with request error
return Promise.reject(error)
})

Authentication for GitHub API v4 with Apollo-Client

GitHub's new GraphQL API requires authentication with a token as the previous version. So, how do we add a 'Header' information into the HttpLink inside Apollo-Client?
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://api.github.com/graphql' }),
cache: new InMemoryCache()
});
Update - 10/2021
Using #apollo/client and graphql packages:
import {
ApolloClient,
InMemoryCache,
gql,
HttpLink
} from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
const token = "YOUR_TOKEN";
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ? `Token ${token}` : null,
},
};
});
const client = new ApolloClient({
link: authLink.concat(
new HttpLink({ uri: "https://api.github.com/graphql" })
),
cache: new InMemoryCache(),
});
client
.query({
query: gql`
query ViewerQuery {
viewer {
login
}
}
`,
})
.then((resp) => console.log(resp.data.viewer.login))
.catch((error) => console.error(error));
Original post - 12/2017
You can define authorization header using apollo-link-context, check the header section
A complete example for using apollo-client for Github API would be :
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
import gql from 'graphql-tag';
const token = "YOUR_ACCESS_TOKEN";
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
});
const client = new ApolloClient({
link: authLink.concat(new HttpLink({ uri: 'https://api.github.com/graphql' })),
cache: new InMemoryCache()
});
client.query({
query: gql`
query ViewerQuery {
viewer {
login
}
}
`
})
.then(resp => console.log(resp.data.viewer.login))
.catch(error => console.error(error));

Categories

Resources