Apollo onError forward(operation) not working - reactjs

I used the example form the Apollo docs:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case 'UNAUTHENTICATED':
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation); }
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
But nothing happens. The second request never works. Maybe it's problem with react native?

try this:
export const logoutLink = onError(({ networkError, operation, forward }) => {
if (networkError?.statusCode === 401) {
return new Observable(observer => {
(async () => {
try {
const newToken = await getToken();
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: `Bearer ${newToken}`,
},
});
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
// Retry last failed request
forward(operation).subscribe(subscriber);
} catch (error) {
observer.error(error);
}
})();
});
}
});

Related

Error handling axios request with recursion

I have a use effect with the helper function registerAccountInConfigurator(token)
acquireTokenForScope([`${process.env.REACT_APP_SW_SCOPE}`]).then(
(token) => {
if (token) {
// console.log(registerAccountInConfigurator(token));
registerAccountInConfigurator(token).then(function (response) {
console.log("response arrived ", response);
});
}
return null;
}
);
In case of an error in the helper function I want to do some steps and then call the function again.
export async function registerAccountInConfigurator(
adb2cToken: string,
change: any = false,
account?: any
) {
try {
const contextRes = await axios.get(
`${process.env.REACT_APP_SW_BASE_URL}/context`,
{
headers: change
? {
"sw-access-key": process.env.REACT_APP_SW_ACCESS_KEY ?? "",
...(adb2cToken !== "" && {
Authorization: `Bearer ${adb2cToken}`,
}),
}
: {},
}
);
const context: any = { ...contextRes.data };
const response = await axios.post(
`${process.env.REACT_APP_SW_BASE_URL}/post-ecommerce/account/register`,
account ?? { storefrontUrl: window.origin },
{ headers: headers(context.token, adb2cToken) }
);
const newToken = response.headers["sw-context-token"];
localStorage.setItem(SW_CONTEXT_TOKEN, newToken);
return response.data;
// Promise.resolve(response.data);
// return new Promise((resolve, reject) => {
// return resolve(response.data);
// });
} catch (error) {
console.log(error);
// do some steps
await registerAccountInConfigurator(adb2cToken, true);
}
}
To artficially test it with an 401 Error I use the paramater change which is per default false and will be set to true in the catch block.
What I dont understand is when I call console.log("response arrived ", response); with setting change to true. I get a normal response, an object. When I set change to false, triggering the catch block, I get undefined as the response value in console.log("response arrived ", response); why is that happening? How can i change that?

Getting status code 304 on a get request with axios using react and redux

I have a get request in my Redux Async Thunk. After calling get to my node.js express server it sends a 304 status code, for some reason I can't get my data.
const userTokenAxios = axios.create({
baseURL: '/api/shoes',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
userTokenAxios.interceptors.response.use((response) => {
if (response.data.errorMessage === 'jwt expired') {
localStorage.removeItem('token');
localStorage.removeItem('user');
}
});
export const getShoesAsync = createAsyncThunk(
'shoes/getShoesAsync',
async (payload, { rejectWithValue }) => {
try {
const response = await userTokenAxios.get('/');
console.log(response);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
Its being called from my homepage:
useEffect(() => {
dispatch(getShoesAsync());
}, [dispatch]);
But I can't get any data as every time the page loads the server sends a 304
my backend controller:
exports.getAllShoes = async (req, res, next) => {
try {
let query = Shoe.find({});
const shoes = await query.populate([
{
path: 'user',
select: 'username',
},
]);
return res.status(200).json(shoes);
} catch (err) {
return next(err);
}
};
app.js in my backend folder:
// ROUTES
app.use('/auth', authRouter);
app.use(
'/api',
expressJwt({ secret: process.env.JWT_SECRET, algorithms: ['HS256'] })
);
app.use('/api/shoes', shoeRouter);
package.json in my client folder
"proxy": "http://localhost:9000"
My network preview:
The problem is your interceptor. Response interceptors must return a value, a rejected promise or throw an error, otherwise the resulting promise will resolve with undefined.
It also seems odd that you're intercepting token errors in the successful response interceptor. I would have assumed you'd use the error interceptor.
userTokenAxios.interceptors.response.use(
res => res, // success response interceptor
err => {
// usually you'd look for a 401 status ¯\_(ツ)_/¯
if (err.response?.data?.errorMessage === "jwt expired") {
localStorage.removeItem('token');
localStorage.removeItem('user');
}
return Promise.reject(err);
}
);
If you are actually responding with a 200 status for token errors, you'd need to handle it in the success interceptor
userTokenAxios.interceptors.response.use(
res => {
if (res.data.errorMessage === "jwt expired") {
localStorage.removeItem('token');
localStorage.removeItem('user');
// Make this look like an Axios error
return Promise.reject({
message: "jwt expired",
response: res,
});
}
return res;
}
);
It also looks like you don't need the trailing forward-slash in your request so simply use
const response = await userTokenAxios.get("");

Apollo GraphQL React refresh token(with AWS amplify) fromPromise --> flatMap() is not being triggered at all

So I followed the documentation from this post to implement the refresh token logic How to refresh JWT token using Apollo and GraphQL
Here's my code:
import Auth from '#aws-amplify/auth';
const getNewToken = () => {
return Auth.currentSession()
.then(data => {
return {
accessToken: data.getAccessToken().getJwtToken(),
refreshToken: data.getRefreshToken().getToken()
};
})
.catch(error => {
console.log('error', error);
});
};
const link = ApolloLink.from([
stateLink,
authLink,
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path, extensions }) => {
if (message.includes('Access Token has expired')) {
console.log('access token has expired');
return fromPromise(
getNewToken().catch(error => {
console.log('error', error)
// Handle token refresh errors e.g clear stored tokens, redirect to login
})
)
.filter(value => Boolean(value))
.flatMap(accessToken => {
console.log('access token!!!', accessToken)
const oldHeaders = operation.getContext().headers;
// modify the operation context with a new token
operation.setContext({
headers: {
...oldHeaders,
authorization: `Bearer ${accessToken}`
}
});
// retry the request, returning the new observable
return forward(operation);
});
}
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
);
});
}
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
linkHttp
]);
But it does not work, the request is not made again and even the console.log('access token!!!', accessToken) is not being called.I don't know why the code inside flatMap() is not being executed. I can't figure out what am I doing wrong, please advise what should I do

Generic function to request api with Axios

I am trying to build a generic function for my endpoints, using Axios and React. Generic because I have always the same header and I do not want to repeat a lot of code for each of my components.
To do that, I built this function (sorry, a lot of comments that I will remove after of course) :
export const getRequest = ( endpoint ) => axios
.get( env._URL_SERVER_ + endpoint, { headers: getHeaders() } )
.then((res) => {
// Success
console.log(res);
return {error: false, response: res.data};
})
.catch((error) => {
// Error
if (error.response) {
/*
* The request was made and the server responded with a
* status code that falls out of the range of 2xx
*/
console.log(error.response.data);
console.log(error.response.status);
return {error: true, status: error.response.status, data: error.response.data};
} else if (error.request) {
/*
* The request was made but no response was received, `error.request`
* is an instance of XMLHttpRequest in the browser and an instance
* of http.ClientRequest in Node.js
*/
console.log(error.request);
return {error: true, data: error.request };
} else {
// Something happened in setting up the request and triggered an Error
console.log('Error', error.message);
return {error: true, data: error.message}
}
});
Ant then in my components I do that :
getSchools = () => {
this.setState({
loadingSchools: true
}, () => {
getRequest(`/schools?name=${this.state.filterByName}&city=${this.state.filterByCity}&school_type_id=${this.state.filterBySchoolTypeId}&page=${this.state.selectedPage}`)
.then((response) => {
// there is an error
if (!response.error) {
this.setState({
schools: response.response.data,
meta: response.response.meta,
links: response.response.links
})
} else {
this.setState({
error: true,
errorMessage: response.data,
})
}
})
.then(() => {
this.setState({loadingSchools : false});
})
})
}
It works fine. I tested it in several situation (all is OK - 200, not found - 404, no response). But is it a good practice ? I feel that there is a lot of codes in the parent component. Maybe I complicate my life?
Here is how I've done it:
var URL_BACKEND = "http://localhost:5000/";
// Create Function to handle requests from the backend
callToBackend = async (ENDPOINT, METHOD) => {
const options = {
url: `${URL_BACKEND}${ENDPOINT}`,
method: METHOD,
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
};
const response = await axios(options);
return response.data;
}
// Then you make a call with the exact endpoint and method:
const response = await this.callToBackend('createSetupIntent', 'POST');
console.log(JSON.stringify(response));
create one common file for base URL let's say api.js
// api.js file code
export const apiUrl = axios.create({
baseURL: 'http://localhost:5000',
});
Register file
// register.js file code
import { apiUrl } from './api';
try {
const resp = await apiUrl.post('/api/register', {
username,
email,
password,
});
const { data, status } = resp;
if (Object.keys(data).length && status === 200) {
// received api data successfully
console.log('API response', data);
}
} catch (err) {
console.log(err);
}
// For auth request
try {
const token = localstorage.getItem('token');
const res = await apiUrl.post(
'/authroute',
{
name: fullName,
originCountry: country,
career: careerStatus,
},
{
headers: { Authorization: `Bearer ${token}` },
}
);
const { data, status } = strapiRes;
if (Object.keys(data).length && status === 200) {
return res.status(status).json(data);
}
} catch (error) {
throw new Error(error);
}
// same for all request
apiUrl.get(endpoint);
apiUrl.post(endpoint, body);
apiUrl.put(endpoint, body);
apiUrl.delete(endpoint, body);

Fetch Post Request not returning payload but return status code (200)

So I am trying to create a user using redux-form. I have an express post route on the backend. NOTE: using redux-thunk for middleware, whatwg-fetch with webpack and babel-polyfill.
routes.post('/signup', async (req, res) => {
try {
const createdUser = await userController.createUser(req.body);
const JSONCreatedUser = JSON.stringify(createdUser);
res.json({
confirmation: 'success',
result: createdUser,
});
return JSONCreatedUser;
} catch (error) {
res.statusMessage = error.toString();
res.status(409).json({
confirmation: 'failure',
error: error.toString(),
});
}
});
So the problem I am having is that when I use postman. I will get the entire user object back.
But when I submit it using form I only get
Apimanager.js
export const signUserUpApi = async (url, params) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const { status, statusText } = response;
if (status === 409) {
throw new Error(statusText);
}
return response;
} catch (error) {
throw new Error(error.toString());
}
};
action.js
import constants from '../constants';
import { signUserUpApi } from '../utils/APIManager';
const signUserUpUrl = process.env.SIGN_USER_UP_URL || 'http://localhost:3000/user/signup';
export const signUserUp = (user) => {
return async (dispatch) => {
try {
const createdUser = await signUserUpApi(signUserUpUrl, user);
dispatch({
type: constants.SIGN_USER_UP,
user: createdUser,
});
return createdUser;
} catch (error) {
throw new Error(error);
}
};
};
export const signUserIn = (user) => {
return {
type: constants.SIGN_USER_UP,
user,
};
};
What I am trying to do is to get the User Object I created when I submit the form and redirect back to the page.
This is what I get back and it did create the user.
First thing, I need is why am I getting the https status code back and not the user object?
Second thing, what are the ways to redirect to the home page when a user successfully signed up logged in.

Resources