I wrote a response with an axios interceptors and send the return value of this response to a js file named handleResponse. This js file takes the return value and returns a result to me. If I get an error, I have it drop to reject.
const instance = axios.create({
baseURL:
apiName === ""
? process.env.VUE_APP_API_URL
: process.env.VUE_APP_API_URL + apiName,
withCredentials: false,
headers: headers
});
instance.interceptors.response.use(
(response) => handleResponse(response),
(error) => console.log(error)
);
My handleResponse js file inside interceptors is as follows
export const handleResponse = (response) => {
return new Promise((resolve) => {
if (response.data["Success"]) resolve(response.data["Payload"]);
else {
let msg = "";
if (response.data["Information"]) msg = response.data["Information"];
showError(msg);
reject(response);
}
});
};
Here I make it fall into catch where I call the api when it drops to the reject operation.
const cancelStorageTransfer = () => {
return StorageTransferRequestService.cancelStorageTransfer(selectedStorageTransfer.value.Id)
.then(() => {
showSuccess("Transfer İptal İşlemi Başarıyla Gerçekleşti")
storageTransferRequestSummary()
}).catch(response => {
showError(response.data.Information)
})
}
I call the api here, but I don't want to use the catch. But when I don't use it, I get "Uncauth(in promise)" error on the log screen.
Here how can I do whether to use the catch at my own will?
Related
I want to customize my get,post,put functions with axios.
After doing the axios.create() operation, when I do the get operation, I want it to perform the then and catch operation there every time.
import axios from "axios";
export default (apiName = "") => {
let headers = {
"Content-Type": "application/json"
};
let token = JSON.parse(localStorage.getItem("token") || "{}");
if (token && token !== "") {
headers.Authorization = `Bearer ${token.AccessToken}`;
}
const instance = axios.create({
baseURL:
apiName === ""
? process.env.VUE_APP_API_URL
: process.env.VUE_APP_API_URL + apiName,
withCredentials: false,
headers: headers
});
return instance;
};
axios.post('/api/Export/CopyShippingPlan/', plan)
.then(handleResponse)
.catch((error) => {
console.log(error);
});
I want to define axios.post here after axios.create and have then and catch action wherever I call axios.post action. So I don't want to have to use then and catch where I use axios every time.
I think that you're looking for interceptors:
response interceptor will be called on every axios response
request interceptor will be called before every axios request
Example:
const ApiService = axios.create()
ApiService.interceptors.response.use(
config => {
//your code
return config
},
error => {
console.log(error)
return Promise.reject(error)
})
ApiService.interceptors.request.use(
config => {
//your code
return config
},
error => {
console.log(error)
return Promise.reject(error)
})
After setting them up you don't need to do anything special just call axios.get, axios.post, etc.
So basically I'm working on a nextjs app which uses authentication. I have a 2 functions which I run on every page load. The first checks if jwt cookies exist and calls another function to validate the tokens if they don't exist. This function is ran from wrapper.getServerSideProps and is passed in the context as ctx. This function works as intended.
export const checkServerSideCookie = (ctx) => {
const access = getCookie("access", ctx.req);
const refresh = getCookie("refresh", ctx.req);
if (access && refresh) {
return checkAuthentication(access, refresh);
} else return { isAuthenticated: false, token: null };
};
The second function is the token validator and this is where the issue arises. I have an object which I intended to update if the validation is successful and leave alone if it isn't. Here is the function
export const checkAuthentication = (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null,
};
const body = JSON.stringify({ token: access });
axios
.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
obj.isAuthenticated = true;
obj.token = access;
})
.catch((err) => {
// call new token function using refresh
console.log("it doesnt work");
});
return obj;
};
The issue is is that the .then does update the object, and when I console.log(obj) in the .then it shows the proper obj to return, however when I return the obj it still holds the initial values of false and null. I don't understand what the issue is. I try doing the return in the .then itself but it throughs this error
TypeError: Cannot destructure property 'isAuthenticated' of 'Object(...)(...)' as it is undefined.
What is the issue here? It all seems good but the updated obj isn't returned.
axios.post is async, you're returning the obj before it gets filled with data from the api response, you can use async/await to solve that :
export const checkAuthentication = async (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null
};
const body = JSON.stringify({ token: access });
try {
const res = await axios.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json"
}
});
obj.isAuthenticated = true;
obj.token = access;
} catch (e) {
// do something with the error
// call new token function using refresh
console.log("it doesnt work");
}
return obj;
};
usage (checkAuthentication now return a promise ) :
checkAuthentication(a, b).then((obj) => {
console.log(obj);
});
When you call checkAuthentication it immediately returns the obj with the default properties. You have an asynchronous operation specified in your function, however you don't wait until it's done. You'd have to rebuild your function the following way:
export const checkAuthentication = (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null,
};
const body = JSON.stringify({ token: access });
return new Promise((resolve, reject) => {
axios
.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
resolve({
isAuthenticated: true,
token: access
})
})
.catch((err) => {
// call new token function using refresh
console.log("it doesnt work");
reject();
});
});
};
and then call your function the following way:
checkAuthentication(access, refresh)
.then(console.log)
.catch(console.log)
You, of course, have multiple options to make your function cleaner, such as by using async/await etc, but this should give you a quick overview of what is wrong.
I have an API (let's call it getToken) to generate a token in its response body. I then call that token and store in the header of another API (let's call it returnBody). It makes sense to use localStorage for the getToken API as this token is re-usable for multiple API's. However, I am having doubts using localStorage if I need to return / display the response body of the succeeding API's such as returnBody. Inside the API's function/command, it logs the response body. However, when I call it via the test file, it generates null.
Sample code below:
commands.js:
Cypress.Commands.add('getToken', () => { //API to generate token
cy.request({
method: 'POST',
url: 'https://someAPItoGenerateToken',
form: true, //sets to application/x-www-form-urlencoded
body: {
grant_type: 'credentials',
scope: 'all-apis'
},
auth: {
username: Cypress.env('api_username'),
password: Cypress.env('api_password')
}
})
.its('body')
.then(bearerToken => {
cy.setLocalStorage('bearerToken', JSON.stringify(bearerToken))
cy.log('Token generated: ' + bearerToken.token)
}
)
})
Cypress.Commands.add('returnBody', (url, token) => { //API to return some values
return cy.request({
method: 'GET',
url: url,
auth: {
bearer: token
}
})
.then((response) => {
// Stringify JSON the body.
let body = JSON.stringify(response.body)
cy.log(body)
})
})
test file:
describe('Return value of 2nd API', ()=> {
before(() => {
cy.getToken() //Run this once to generate token for the entire test suite
cy.saveLocalStorage()
})
beforeEach(() => {
cy.restoreLocalStorage()
})
it('Return value of 2nd API', () => {
cy.getLocalStorage('bearerToken').should('exist')
cy.getLocalStorage('bearerToken').then(bearerToken => {
const tokenJSON = JSON.parse(bearerToken)
const url = 'https://someAPItoReturnJSONbody'
cy.returnBody(url, tokenJSON.token).then((returned_value) => {
cy.log(returned_value)
})
})
})
})
body from the returnBody command returns the JSON response. However, returned_value from the test file displays null.
As commented in this issue: "You cannot return a 3rd party promise from a custom command because this breaks the chaining between cypress commands. This is because the .then methods are not the same."
So, simply return the request body as:
Cypress.Commands.add('returnBody', (url, token) => {
return cy.request({ /* options */ })
.its("body");
});
Then, in your test you can do:
it("should return foo value", () => {
cy.returnBody(url, token).then(returned_value => {
cy.log(returned_value);
expect(returned_value).to.deep.equal("foo-value");
})
})
You may need to return response body from your returnBody task:
Cypress.Commands.add('returnBody', (url, token) => {
return cy.request({ /* options */ })
.then(response => {
let body = JSON.stringify(response.body);
Cypress.log(body);
return body; // Add this line
});
});
An alternative would be to store the token on cypress side using fixtures.
fixture.json:
{"bearerToken":""
.
.
.}
commands.js:
cy.fixture('testData.json').as('testData')
.
.
.then(bearerToken => {
this.testData.bearerToken = JSON.stringify(bearerToken)
cy.log('Token generated: ' + bearerToken.token)
}
)
test.js
describe('Return value of 2nd API', ()=> {
before(() => {
cy.getToken() //Run this once to generate token for the entire test suite
})
it('Return value of 2nd API', () => {
cy.fixture('testData.json').as('testData')
.then(testData => {
const tokenJSON = JSON.parse(testData.bearerToken)
const url = 'https://someAPItoReturnJSONbody'
cy.returnBody(url, tokenJSON.token).then((returned_value) => {
cy.log(returned_value)
})
})
})
})
this is a very weird problem! I'm trying to build a login form which sets a JWT token in localstorage. Other forms then use that token to post requests. I can see the token in my console.log just fine, but sometimes (like 3 out of 5 times), when I am setting localstorage.getitem('idToken'), it shows as null. This behavior most noticeably happens when I remove the console.log(idToken) from my loginUser() function (code in actions.js file - given below). What am I doing wrong? my app is built using React/Redux.
action.js
export function loginUser(creds) {
const data = querystring.stringify({_username: creds.username, _password: creds.password});
let config = {
method: 'POST',
headers: { 'Content-Type':'application/x-www-form-urlencoded' },
body: data
};
return dispatch => {
// We dispatch requestLogin to kickoff the call to the API
dispatch(requestLogin(creds));
return fetch(BASE_URL+'login_check', config)
.then(response =>
response.json().then(user => ({ user, response }))
).then(({ user, response }) => {
if (!response.ok) {
// If there was a problem, we want to
// dispatch the error condition
dispatch(loginError(user.message));
return Promise.reject(user)
} else {
localStorage.setItem('idToken', user.token);
let token = localStorage.getItem('idToken')
console.log(token);
// if I remove this log, my token is returned as null during post.
dispatch(receiveLogin(user));
}
}).catch(err => console.log("Error: ", err))
}
}
here's my POST request:
import axios from 'axios';
import {BASE_URL} from './middleware/api';
import {reset} from 'redux-form';
let token = localStorage.getItem('idToken');
const AuthStr = 'Bearer '.concat(token);
let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr }
};
export default (async function showResults(values, dispatch) {
console.log(AuthStr);
axios.post(BASE_URL + 'human/new', values, headers)
.then(function (response) {
console.log(response);
alert("Your submit was successful");
//dispatch(reset('wizard'));
}).catch(function (error) {
console.log(error.response);
alert(error.response.statusText);
});
});
This GET request works everytime, BTW:
getHouses = (e) => {
let token = localStorage.getItem('idToken') || null;
const AuthStr = 'Bearer '.concat(token);
axios.get(BASE_URL + 'household/list', { headers: { Authorization: AuthStr } }).then((response) =>
{
let myData = response.data;
let list = [];
let key =[];
for (let i = 0; i < myData._embedded.length; i++) {
let embedded = myData._embedded[i];
list.push(embedded.friendlyName);
key.push(embedded.id);
}
this.setState({data: list, key: key});
})
.catch((error) => {
console.log('error' + error);
});
}
I'm at my wit's end! Please help!
The localStorage.setItem() is a asynchronous task, and sometimes you run let token = localStorage.getItem('idToken') just after the setItem will fail, so you get a null, so please put the getItem operation some later, have a try, it will be different :
setTimeout(function() {
let token = localStorage.getItem('idToken');
dispatch(receiveLogin(user));
}, 50);
Move your token logic (i.e. localStorage.getItem('idToken');) inside the exported function and it should work
export default (async function showResults(values, dispatch) {
let token = localStorage.getItem('idToken');
const AuthStr = 'Bearer '.concat(token);
let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr
}
};
axios.post(BASE_URL + 'human/new', values, headers)...
There can't be a case where you set a key value in localstorage and then it returns you null, immediately in the next line.
localStorage.setItem('idToken', user.token);
let token = localStorage.getItem('idToken');
This will only happen if your user.token value is null.
Maybe the case here is your thennable function not returning value to your next then like this:
....
.then(response =>
// return response to your next then function
// this will be passed to next then function as params
return response.json();
).then(({ user, response }) => {
....
Make a function whose return the value or a default value
const [hideTyC, setHideTyC] = useState(false);
const loadTyCFlag = (): any => {
if (
localStorage.getItem("tyc") !== null ||
localStorage.getItem("tyc") !== undefined
) {
return localStorage.getItem("tyc") || false;
}
};
useIonViewDidEnter(() => {
hideTabBar();
setHideTyC(loadTyCFlag());
});
I'm trying to test this simple api module:
import fetch from 'isomorphic-fetch';
export const getJson = (endpoint: string) => {
const options = { credentials: 'include', method: 'GET' };
return fetch(endpoint, options)
.then(response => response.json()
.then(json => {
if (response.ok) return json;
return Promise.reject(json.errors);
})
)
.catch(error => {
if (error.constructor === Array) return error;
return [error.message];
});
};
With this test, where I'm mocking fetch:
import { getJson } from '../api';
const mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
'Content-type': 'application/json'
}
});
};
describe('api middleware', () => {
describe('getJson', () => {
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, body)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
But the test fails with:
Expected value to equal:
{"data": ["data"], "meta": {}}
Received:
["Unexpected end of JSON input"]
Difference:
Comparing two different types of values:
Expected: object
Received: array
I've not been able to figure out why this isn't working. Why am I receiving the "Unexpected end of JSON input" error? And how do I successfully mock fetch locally in a test? In this medium post it's done in basically the same way..
So apparently the test was still using the global fetch library, and not my patched version. The solution was to:
Remove the 'isomorphic-fetch' mock (in __mocks__ at the root of the project).
Import 'isomorphic-fetch' once at the root of my project with import 'isomorphic-fetch;
Remove the 'isomorphic-fetch' import at the top of my api module (since it's already imported at the entrypoint
Update the test to:
test:
// to make the Response constructor available
import 'isomorphic-fetch';
import { getJson } from '../api';
describe('api middleware', () => {
describe('getJson', () => {
beforeEach(() => {
window.fetch = jest.genMockFunction();
});
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
const init = { status: 200, statusText: 'OK' };
window.fetch.mockReturnValueOnce(Promise.resolve(new Response(body, init)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
Most probably because your getJson function does not use the global (window) fetch.
The way I would suggest doing it is to use Dependency Injection (DI); make getJson retrieve the the "http request" library/function (in your case fetch) and in your tests, create a mock function which is injected. The mock function will return the data that you want as part of testing.