My Axios Response Interceptor Is Failing? - javascript

Could you help me to figure this out.
I need to refresh my token on error and retry failed requests..
I have:
axios.interceptors.response.use(function(response) {
return response;
}, function(error) {
const originalRequest = error.config;
if(error.response.status===401 && !originalRequest._retry) {
originalRequest._retry = true;
setTimeout(async function() {
const refreshedHeader = await setHeader()
console.log('New header: ', refreshedHeader)
axios.defaults.headers = refreshedHeader
originalRequest.headers = refreshedHeader
console.log('Original Request: ', originalRequest)
return axios(originalRequest)
}, 2000);
}
return Promise.reject(error);
});
setHeader() is this:
const setHeader = async () => {
const token = await refreshToken()
const header = {
Authorization: `Bearer ${token}`,
};
return header
}
The new token is pulled and the header is updated, but the retry fails with 401 still?

It is because of the timeout. This:
return Promise.reject(error);
will always return an error before the timeout has done its job.
Try to remove that timeout and set the error function to async.
axios.interceptors.response.use(function(response) {
return response;
}, async function(error) {
const originalRequest = error.config;
if (error.response.status===401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshedHeader = await setHeader()
console.log('New header: ', refreshedHeader)
axios.defaults.headers = refreshedHeader
originalRequest.headers = refreshedHeader
console.log('Original Request: ', originalRequest)
return axios(originalRequest);
}
return Promise.reject(error);
});
Else if necessary try something like:
axios.interceptors.response.use(function(response) {
return response;
}, async function(error) {
await new Promise(function(res) {
setTimeout(function() {res()}, 2000);
});
const originalRequest = error.config;
if (error.response.status===401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshedHeader = await setHeader()
console.log('New header: ', refreshedHeader)
axios.defaults.headers = refreshedHeader
originalRequest.headers = refreshedHeader
console.log('Original Request: ', originalRequest)
return axios(originalRequest);
}
return Promise.reject(error);
});
This will block all of the rest for 2 seconds and then resolve or reject it.

Related

Call a method inside Promise.all from Redux Saga

In the code block below, var ret=that.sendSMTPEmailForOrderPlaced(orderData); is not getting executed. The console is printing "before calling," but it is not printing "inside sendSMTPEmailForOrderPlaced" message. Getting error TypeError: Cannot read property 'sendSMTPEmailForOrderPlaced' of null in createNewOrderHistory method.createNewOrderHistory is called from Redux Saga
const result = yield call(MyProfileRepository.createNewOrderHistory, data);
What is wrong with the code below?
class MyRepository {
constructor(callback) {
this.callback = callback;
}
createNewOrderHistory(tableData) {
var that = this;
const AuthStr = 'Bearer ' + getToken();
let promises = [];
tableData.map((tableData, index) => {
var data = {
invoice_id: tableData.invoiceID.toString(),
};
promises.push(axios.post(`url`, data, {
headers: { Authorization: AuthStr },
}));
});
return Promise.all(promises).then(function(results) {
console.log("before calling")
var ret = that.sendSMTPEmailForOrderPlaced(orderData);
console.log("after calling")
console.log(ret);
return (results);
}).catch(error => {
return (error);
});
}
sendSMTPEmailForOrderPlaced(data) {
console.log("inside sendSMTPEmailForOrderPlaced")
const response = axios.post(`url`, data).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
return (error);
});
return response.data;
return null;
}
}
export default new MyRepository();
It's hard to test your code, but I believe that #Keith had the right idea in his comment. So to test it I had to change 'url' and so on. But this code should give you a good idea on how to write it:
const axios = require('Axios');
class MyRepository {
async createNewOrderHistory(tableData) {
var that = this;
const AuthStr = 'Bearer '; // + getToken();
const header = { headers: { Authorization: AuthStr } };
let promises = tableData.map((tableData, index) => {
var data = { invoice_id: tableData.invoiceID.toString() };
return axios.post('https://jsonplaceholder.typicode.com/posts', data, header);
});
const results = await Promise.all(promises).then(async (results) => {
console.log("before calling")
var ret = await that.sendSMTPEmailForOrderPlaced(results.data);
console.log("after calling", ret);
return (results);
}).catch(error => {
return (error);
});
console.log(results.map(a => a.data));
}
async sendSMTPEmailForOrderPlaced(data) {
console.log("inside sendSMTPEmailForOrderPlaced")
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', data);
return response.data;
} catch (error) {
return error;
}
}
}
var repo = new MyRepository();
repo.createNewOrderHistory([{ invoiceID: 'test' }, { invoiceID: 'test2' }, { invoiceID: 'test3' }]);
If you want to run this, past it into a test.js file in an empty folder, then run the following in the same folder:
npm init -y
npm i axios
node .\test.js

AWS Lambda call 3 async functions by 1 Lambda call

Ok, i'm done. Please someone help me :(
I don't know how js and lambda works
What i have to do:
Send GET request and get response.
Write data from response to DynamoDb
I can do it 1by1 but can't do everything by 1 lambda call.
My code:
const https = require('https');
const crypto = require("crypto");
const AWS = require('aws-sdk');
const DynamoDb = new AWS.DynamoDB({region: 'eu-central-1'});
exports.handler = async (event) => {
let response;
console.log("Start");
let steamTicket;
let steamId;
if(event.body){
const body = JSON.parse(event.body);
if(body.steamticket && body.steamid){
steamTicket = body.steamticket;
steamId = body.steamid;
}
else{
response = {
statusCode: 400,
body: JSON.stringify({
authenticated: false,
reason: 'cant find steamid or steamticket in your request'
})
};
return response;
}
}
else{
response = {
statusCode: 400,
body: JSON.stringify({
authenticated: false,
reason: 'cant find request body'
})
};
return response;
}
await httprequest(steamTicket).then((data) =>{
if(data.response && data.response.params){
if(data.response.params.result == 'OK' && data.response.params.steamid == steamId){
console.log(JSON.stringify(data));
const sessionId = crypto.randomBytes(16).toString("hex");
console.log('Generated session id: ' + sessionId);
PutToDB(sessionId, steamId);
}
else{
response = {
statusCode: 400,
body: JSON.stringify({
authenticated: false,
reason: 'steam response is not OK or session != steamId'
})
};
return response;
}
}
else{
response = {
statusCode: 400,
body: JSON.stringify({
authenticated: false,
reason: 'invalid response from steam: ' + JSON.stringify(data)
})
};
return response;
}
});
};
async function PutToDB(sessionId, steamId){
var WriteParams = {
RequestItems:{
SteamSessions: []
}
};
WriteParams.RequestItems.SteamSessions.push({
PutRequest:{
Item: {
SteamId: {S: steamId},
SessionId: {S: sessionId},
ttl: {N: (Math.floor(Date.now() / 1000) + 600).toString()}
}
}
});
console.log('SessionIdToWrite: ' + sessionId);
return new Promise((resolve, reject) =>{
DynamoDb.batchWriteItem(WriteParams, function(err, data){
if(err){
console.log("Error", err);
}
else{
console.log("Success write", JSON.stringify(data));
}
})
})
}
async function httprequest(steamTicket) {
return new Promise((resolve, reject) => {
const options = {
host: 'partner.steam-api.com',
path: '/ISteamUserAuth/AuthenticateUserTicket/v1/?key=somekey&appid=someid&ticket=' + steamTicket,
port: 443,
method: 'GET'
};
const req = https.request(options, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
}
resolve(body);
});
});
req.on('error', (e) => {
reject(e.message);
});
// send the request
req.end();
});
}
I lost way already, i'm not even sure it should work like that.
And most confusing thing! This b give me this test results:
Run 1:
2021-03-05T13:28:47.741Z INFO Start
2021-03-05T13:28:48.612Z INFO {"response":{"params":{"result":"OK","steamid":"mysteamid","ownersteamid":"mysteamid","vacbanned":false,"publisherbanned":false}}}
2021-03-05T13:28:48.650Z INFO Generated session id: 6a5633a5f862d8663d0fe546a9c89feb
2021-03-05T13:28:48.650Z INFO SessionIdToWrite: 6a5633a5f862d8663d0fe546a9c89feb
DynamoDb is empty, here we can't see log from DynamoDb.batchWriteItem result.
Run 2:
2021-03-05T13:29:53.308Z INFO Start
2021-03-05T13:29:53.674Z INFO Success write {"UnprocessedItems":{}}
2021-03-05T13:29:54.048Z INFO {"response":{"params":{"result":"OK","steamid":"mysteamid","ownersteamid":"mysteamid","vacbanned":false,"publisherbanned":false}}}
2021-03-05T13:29:54.048Z INFO Generated session id: 05c62de782202fc100cea9d47e38242c
2021-03-05T13:29:54.048Z INFO SessionIdToWrite: 05c62de782202fc100cea9d47e38242c
And after second run i can see in DynamoDb sessionId from FIRST RUN (6a5633a5f862d8663d0fe546a9c89feb)
If i run it again, there will be id from 2nd run
I think it continues to run previous tasks on new run? Or what? I'm lost
Thank you for any help with it
You need to call reject / resolve in the DynamoDb.batchWriteItem call.
return new Promise((resolve, reject) =>{
DynamoDb.batchWriteItem(WriteParams, function(err, data){
if(err){
console.log("Error", err);
reject(err);
}
else{
console.log("Success write", JSON.stringify(data));
resolve();
}
})
})

Trying to Create localStorage Not Working (NextJS)

I need to pull inventory data to my NextJS app, I am then trying to store that data to localStorage, but it never gets set. What am I doing wrong?
if (process.browser && localStorage.getItem('inv') === undefined) {
const inv = await getInventory()
localStorage.setItem('inv', inv)
}
getInventory() is just a node script that pulls inventory data from a remote api and returns fullInventory.
Here is the code from getInventory():
const getInventory = async () => {
const token = await refreshToken()
const header = {
Authorization: `Bearer ${token}`,
};
const queries = await getQueriesCount();
axios.interceptors.response.use(function (response) {
return response;
}, async function (error) {
await new Promise(function (res) {
setTimeout(function () { res() }, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshedHeader = await setHeader()
axios.defaults.headers = refreshedHeader
originalRequest.headers = refreshedHeader
return Promise.resolve(axios(originalRequest));
}
return Promise.reject(error);
});
let fullInventory = [];
for (let i = 0; i < queries; i++) {
setTimeout(async () => {
try {
const res = await axios({
url: `${MyApi}/Item.json&offset=${i * 100}&customSku=!~,`,
method: "get",
headers: header,
});
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const items = await res.data.Item;
items[0]
? (fullInventory = fullInventory.concat(items))
: fullInventory.push(items);
if (i + 1 === queries) {
return fullInventory;
}
} catch (error) {
console.error("We have a problem here: ", error.response);
}
}, 2000 * i);
};
}

Way to handle refresh token

I am working on login feature and have problem when refresh token.
When token expire making request to refresh token, remove the old token, and save the new token to AsyncStorage.
After login successfully have to function A and B. The function A is using the new token to make its request. the function B say that it need to refresh the token so make request to refresh token ( the request make successfully, token being refresh) but The token that request A is using now invalid - I think it happens due to asynchronous
This is my code that use to refresh token:
axiosInstance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status === CODE_TOKEN_EXPIRED) {
try {
const token = await authenticationService.getRefreshToken();
const response = await authenticationService.refreshToken(token);
await authenticationService.removeToken();
await authenticationService.storeToken(response.data.params.access_token);
await authenticationService.storeRefreshToken(response.data.params.refresh_token);
error.config.headers.Authorization = 'Bearer ' + response.data.params.access_token;
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.params.access_token;
return axiosInstance(error.config);
} catch (err) {
console.log(2, err);
await authenticationService.removeToken();
navigationService.navigate('LoginForm');
}
}
return Promise.reject(error);
}
);
Anyone know how to handle which asynchronous call for refresh token?
First would be for you to check if you are changing token to the correct axios instance. It is necessary to change Authorization header on error.response config as you did, but also for main axios instance (if you have one) like so: axios.defaults.headers.common["Authorization"] = "Bearer " + access_token;
If it is multiple parallel requests going on that could possibly need to be postponed after token is refreshed issue and answer gets complex, but check this gist with full refresh logic with axios.
I have implemented the same scenario in fetch API. you can also do this same in axios API. Try this to avoid interceptor concept.
Api.ts
export const api = ({ method, url, body, isProtected = true }) => {
return new Promise((resolve, reject) => {
const payload = {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
};
if (body !== null) {
(payload as any).body = JSON.stringify(body);
}
/**
* "isProtected" is used for API call without authToken
*/
if (isProtected) {
AsyncStorage.getItem(ACCESS_TOKEN).then(accessKey => {
(payload.headers as any).Authorization = `Bearer ${accessKey}`;
fetch(url, payload)
.then((response: any) => {
/*
* 419 status denotes the timeout of authToken
*/
if (response.status == 419) {
// refresh token
AsyncStorage.getItem(REFRESH_TOKEN).then(refreshKey => {
const payloadRef = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + refreshKey
}
};
/*
* This call refresh the authToken using refreshing call to renew the authToken
*/
fetch(URL.baseUrl + "/refresh", payloadRef)
.then((response: any) => response.json())
.then(response => {
/*
* if refresh token expired. redirect to login page
*/
if (response.status !== codes.SUCCESS) {
if (!User.sessionOver) {
User.sessionOver = true;
Alert.alert(
'Alert',
'Session Timeout',
[
{
text: 'Get back to Login',
onPress: () => {
// get to Login page
}
}
],
{ cancelable: false }
);
}
} else if (response.status == codes.SUCCESS) {
/*
* If refresh token got refreshed and set it as authToken and retry the api call.
*/
AsyncStorage.setItem(ACCESS_TOKEN, response.payload.access_key).then(() => {
(payload.headers as any).Authorization = 'Bearer ' + response.payload.access_key;
fetch(url, payload)
.then(response => response.json())
.then(response => {
if (response.status == codes.SUCCESS) {
resolve(response);
}
})
.catch(error => {
reject(error);
});
});
}
});
});
} else {
resolve(response.json());
}
})
.catch(error => {
reject(error);
});
});
} else {
fetch(url, payload)
.then((response: any) => {
response = response.json();
resolve(response);
})
.catch(error => {
reject(error);
});
}
});
};
MovieService.ts
import { api } from '../services/api';
import { URL } from '../config/UrlConfig';
const getMovies = () => {
const method = 'GET';
const url = URL.baseUrl + '/v1/top/movies';
const body = null;
const isProtected = true;
return api({ method, url, body, isProtected });
};
export { getMovies };
Maybe it will helps - https://gist.github.com/ModPhoenix/f1070f1696faeae52edf6ee616d0c1eb
import axios from "axios";
import { settings } from "../settings";
import { authAPI } from ".";
const request = axios.create({
baseURL: settings.apiV1,
});
request.interceptors.request.use(
(config) => {
// Get token and add it to header "Authorization"
const token = authAPI.getAccessToken();
if (token) {
config.headers.Authorization = token;
}
return config;
},
(error) => Promise.reject(error)
);
let loop = 0;
let isRefreshing = false;
let subscribers = [];
function subscribeTokenRefresh(cb) {
subscribers.push(cb);
}
function onRrefreshed(token) {
subscribers.map((cb) => cb(token));
}
request.interceptors.response.use(undefined, (err) => {
const {
config,
response: { status },
} = err;
const originalRequest = config;
if (status === 401 && loop < 1) {
loop++;
if (!isRefreshing) {
isRefreshing = true;
authAPI.refreshToken().then((respaonse) => {
const { data } = respaonse;
isRefreshing = false;
onRrefreshed(data.access_token);
authAPI.setAccessToken(data.access_token);
authAPI.setRefreshToken(data.refresh_token);
subscribers = [];
});
}
return new Promise((resolve) => {
subscribeTokenRefresh((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(axios(originalRequest));
});
});
}
return Promise.reject(err);
});
export default request;

Axios interceptor refresh token for multiple requests

I'll throw the http request because I'm calling the refresh token when it returns 401. After the refresh token response, I need to throw the previous request
SAMPLE
Logın -> — 1 hours later— —> call product —> 401 —> call refresh token —> call product
I try this link a link and look this link a link but doesn't work.
Catch the 401 error
setInterceptors = () => {
axios.interceptors.response.use(
response => {
return response;
},
err => {
return new Promise((resolve, reject) => {
if (err.response.status === 401 && err.config && !err.config.__isRetryRequest) {
const originalRequest = err.config;
this.emit('onAutoLogin', originalRequest);
}
// throw err;
});
}
);
};
Call my action
jwtService.on('onAutoLogin', originalRequest => {
jwtService
.signInWithToken()
.then(res => {
if (res.access_token) {
originalRequest.headers['Authorization'] = 'Bearer ' + res.access_token;
Axios.request(originalRequest).then(response => {
store.dispatch({
type: ** MY PROBLEM İS HERE **
payload: response.data
});
});
}
})
.catch(err => {
jwtService.setSession(null);
});
using this link I was able to solve the problem without triggering the redux store.
let isRefreshing = false;
let failedQueue = [];
const processQueue = (error, token = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
axios.interceptors.response.use(
response => {
return response;
},
err => {
const originalRequest = err.config;
if (err.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function(resolve, reject) {
failedQueue.push({ resolve, reject });
})
.then(token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
return axios(originalRequest);
})
.catch(err => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function(resolve, reject) {
axios
.post('/fooUrl/refreshToken', {
refreshToken: "fooToken"})
.then(({ data }) => {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.fooToken;
originalRequest.headers['Authorization'] = 'Bearer ' + data.fooToken;
processQueue(null, data.fooToken);
resolve(axios(originalRequest));
})
.catch(err => {
processQueue(err, null);
store.dispatch(showMessage({ message: 'Expired Token' }));
reject(err);
})
.then(() => {
isRefreshing = false;
});
});
}
return Promise.reject(err);
}
);

Categories