Angular 2 - Sequential HTTP requests - javascript

I am trying to implement two sequential HTTP requests in my Angular 2 App.
When the user click the login button I want to save in LocalStorage the refresh and access tokens and then execute one more HTTP request in order to get User info. I saw this post but it doesn't work in my case.
Angular 2 - Chaining http requests
This was my initial implementation for login and getMe service:
How can I merge the requests bellow?
login.component.ts
this.authenticationService.login(this.user.email, this.user.password)
.subscribe(
data => {
// this.alertService.success('Registration successful', false);
this.router.navigate([this.returnUrl]);
},
error => {
console.log(error);
this.alertService.error(error);
this.loading = false;
});
authentication.service.ts
login(email: string, password: string) {
let headers = new Headers({
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({ headers: headers });
return this.http.post(this.config.apiUrl + this.authenticationUrl + '/login',
JSON.stringify({ email: email, password: password }), options)
.map((response: Response) => {
let tokens = response.json();
if (tokens && tokens.token && tokens.refreshToken) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('tokens', JSON.stringify(tokens));
}
});
}
user.service.ts
getMe() {
console.log('getMe');
return this.http.get(this.config.apiUrl + this.usersUrl + '/me', this.jwt()).map((response: Response) => {
let user = response.json();
if (user)
localStorage.setItem('currentUser', JSON.stringify(user));
});
}

You are returning an Observable on your login method, so in your data subscription just call getMe() from your user service.
this.authenticationService.login(this.user.email, this.user.password)
.subscribe(
data => {
// Check to make sure data is what you expected, then call getMe()
this.userService.getMe();
this.router.navigate([this.returnUrl]);
},
error => {
console.log(error);
this.alertService.error(error);
this.loading = false;
});
This is a fairly blunt and un-exciting way to approach that problem. As mentioned, mergeMap could serve you well here.
Check out this quick example. I recommend playing around with the Observable data to get a feel for what is happening. The Observable.of are basically acting like fake network request in this case.
http://jsbin.com/pohisuliqo/edit?js,console

Related

Why does my Firebase function successfully make an API request, but then timeout

I built a React web app that allows users the calculate their meal's nutrition.
Users enter an ingredient in the IngredientForm component, triggering the fetchFirebaseNutrition function.
const fetchFirebaseNutrition = (ingredientName) => {
const axios = require('axios');
const options = {
method: 'GET',
url: 'http://localhost:5001/nutrition-calculator-6db9d/us-central1/fetchAPINutrition',
params: { ingredient: ingredientName },
};
return axios
.request(options)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error);
return setIsAPIConnectionDown(true);
});
};
fetchFirebaseNutrition makes a get request to to my Firebase function, fetchAPINutrition, with the relevant ingredient. Then, fetchAPINutrition makes a get request to an API for the nutrition data. I'm using the Firebase function to conceal the API key for when I deploy my web app.
exports.fetchAPINutrition = functions.https.onRequest((req, res) => {
const axios = require('axios');
const ingredient = req.query.ingredient;
const options = {
method: 'GET',
url: 'https://calorieninjas.p.rapidapi.com/v1/nutrition',
params: { query: ingredient },
headers: {
'X-RapidAPI-Host': 'calorieninjas.p.rapidapi.com',
'X-RapidAPI-Key': process.env.REACT_APP_RAPID_API_KEY,
},
};
return axios
.request(options)
.then((response) => {
console.log(response.data);
return response.data;
})
.catch((error) => {
console.error(error);
});
});
fetchAPINutrition works... kind of. When I make a request to the function from the front-end, I can see on the Firebase Emulator that fetchAPINutrition is successfully getting the data from the API. See the
Firebase Emulator Logs.
However, the function just times out afterwards. So, fetchFirebaseNutrition never receives the API data and just returns an Axios Network Error message.
How can I stop my Firebase function from timing out and return the API data to the front-end?
Any help would be immensely appreciated!
HTTPS functions expect a return, which you don't. Instead, you are returning a promise without waiting for it to complete.
You need to return a result.
res.status(200).send(response.data);
In this case;
axios
.request(options)
.then((response) => {
res.status(200).send(response.data);
})
.catch((error) => {
console.error(error);
res.status(500).send("Error");
});

Difference between Python requests POST and axios POST

I am having a difficult time understanding why my API call does not work in axios (relatively new to JS). I have built an API server that takes in an Authorization header with a JWT token.
Here is my POST request workflow in Python:
resp = requests.post('http://127.0.0.1:8000/api/v1/login/access-token', data={'username': 'admin#xyz.com', 'password': 'password'})
token = resp.json()['access_token']
test = requests.post('http://127.0.0.1:8000/api/v1/login/test-token', headers={'Authorization': f'Bearer {token}'})
# ALL SUCCESSFUL
Using axios:
const handleLogin = () => {
const params = new URLSearchParams();
params.append('username', username.value);
params.append('password', password.value);
setError(null);
setLoading(true);
axios.post('http://localhost:8000/api/v1/login/access-token', params).then(response => {
console.log(response)
setLoading(false);
setUserSession(response.data.access_token);
props.history.push('/dashboard');
}).catch(error => {
setLoading(false);
console.log(error.response)
if (error.response.status === 401) {
setError(error.response.data.message);
} else {
setError("Something went wrong. Please try again later.");
}
});
}
// the above works fine
// however:
const [authLoading, setAuthLoading] = useState(true);
useEffect(() => {
const token = getToken();
if (!token) {
return;
}
axios.post(`http://localhost:8000/api/v1/login/test-token`, {
headers: {
'Authorization': 'Bearer ' + token
}
}).then(response => {
// setUserSession(response.data.token);
console.log('we made it')
setAuthLoading(false);
}).catch(error => {
removeUserSession();
setAuthLoading(false);
});
}, []);
if (authLoading && getToken()) {
return <div className="content">Checking Authentication...</div>
}
// RETURNS A 401 Unauthorized response...
What is different about the two above requests? Why does the axios version return different results than requests?
In my API, CORS have been set to *, and I know that the token within Axios is being saved properly in sessionStorage.
Any ideas?
As far as I can see you are passing your username and password in axios as params and as body data in your python request, I am not sure if your backend expects it as params or body data but try changing const params = new URLSearchParams(); to
const params = new FormData(); if the problem is that the backend isn't getting the body data it needs. The best thing I could recommend is checking your browser network tab and seeing what exactly the problem is when you hit your server.

Vue SPA retrieve status code on error codes (not 200) in nested promise

In my VUE components, I use this async method to fetch data from API:
Components:
methods: {
async fetch() {
// console.log("##### WAIT ####");
const { data } = await staffRepository.getItems(this.teamId)
// console.log("##### END WAIT ####");
this.staffs = data
},
},
As you can see I use a custom repository to have a single axios code, this repository is imported in my previous component.
staffRepository:
export default {
getItems(nationId) {
return Repository.get(`page/${nationId}`)
},
}
And finally the main repository having the axios code:
Repository:
import axios from 'axios/index'
const baseDomain = 'https://my end point'
const baseURL = `${baseDomain}`
...
const headers = {
'X-CSRF-TOKEN': token,
// 'Access-Control-Allow-Origin': '*', // IF you ADD it add 'allowedHeaders' to ai server config/cors.php
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtoken}`,
}
export default axios.create({
baseURL,
withCredentials: withCredentials,
headers: headers,
})
This code works very nice when the jwtoken is a valid and NOT EXIPRED token.
The problem is when the token is expired or not found and my laravel 5.8 API returns the status code 401 (or other).
GET https://api.endpoint 401 (Unauthorized)
A good solution could catch the status code in staffRepository, the one having the get method.
MySolution: (not working)
getItems(nationId) {
return Repository.get(`page/${nationId}`)
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response.status) // <-- it works!
})
},
This could be nice because in error case the error in console is 401
But I can't use this solution because I have 2 nested promises: this one and the async fetch() into the component.
How can I fix it still using my repository environment?
I would suggest using the returned promise in your component, to make things more explicit:
methods: {
fetch() {
let data = null
staffRepository
.getItems(this.teamId)
.then(data => {
// do something with data
this.staffs = data
})
.catch(e => {
// do something with error, or tell the user
})
},
},
Edit - this will work perfectly fine, as your method in Repository will return a promise by default if you are using axios.
Try this: API code, where HTTP is an axios instance
export const get = (path: string): Promise<any> => {
return new Promise((resolve, reject) => {
HTTP.get(`${path}`)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(handleError(error));
});
});
};
// ***** Handle errors *****/
export function handleError(error) {
if (error.response) {
const status = error.response.status;
switch (status) {
case 400:
// do something
break;
case 401:
// do something, maybe log user out
break;
case 403:
break;
case 500:
// server error...
break;
default:
// handle normal errors here
}
}
return error; // Return the error message, or whatever you want to your components/vue files
}
The best practice solution is to use axios's interceptors:
import axios from "axios";
import Cookies from "js-cookie";
export default (options = {}) => {
let client = options.client || axios.create({ baseURL: process.env.baseUrl });
let token = options.token || Cookies.get("token");
let refreshToken = options.refreshToken || Cookies.get("refreshToken");
let refreshRequest = null;
client.interceptors.request.use(
config => {
if (!token) {
return config;
}
const newConfig = {
headers: {},
...config
};
newConfig.headers.Authorization = `Bearer ${token}`;
return newConfig;
},
e => Promise.reject(e)
);
client.interceptors.response.use(
r => r,
async error => {
if (
!refreshToken ||
error.response.status !== 401 ||
error.config.retry
) {
throw error;
}
if (!refreshRequest) {
refreshRequest = client.post("/auth/refresh", {
refreshToken
});
}
const { data } = await refreshRequest;
const { token: _token, refreshToken: _refreshToken } = data.content;
token = _token;
Cookies.set("token", token);
refreshRequest = _refreshToken;
Cookies.set("refreshToken", _refreshToken);
const newRequest = {
...error.config,
retry: true
};
return client(newRequest);
}
);
return client;
};
Take a look at client.interceptors.response.use. Also you should have a refreshToken. We are intercepting 401 response and sending post request to refresh our token, then waiting for a new fresh token and resending our previous request. It's very elegant and tested solution that fits my company needs, and probably will fit your needs too.
To send request use:
import api from './api'
async function me() {
try {
const res = await api().get('/auth/me')
// api().post('/auth/login', body) <--- POST
if (res.status === 200) { alert('success') }
} catch(e) {
// do whatever you want with the error
}
}
Refresh token: The refresh token is used to generate a new access
token. Typically, if the access token has an expiration date, once it
expires, the user would have to authenticate again to obtain an access
token. With refresh token, this step can be skipped and with a request
to the API get a new access token that allows the user to continue
accessing the application resources.

Unauthorized when fetching Tasks with Microsoft graph

I want to fetch my tasks within Javascript and possibly add new ones, but let's focus on fetching a task.
I made an app and use the msal.js file to get a token. I get prompted to allow the app to read/write from my account, the popup closes and I've obtained a token!
So far so good, but when I try to fetch my tasks the API responds with "unauthorized". When I check the headers I can see I sent along "bearer [token]" however.
I'm completely clueless on how to get my tasks by now since I did get a proper token and I've followed the guided setup to make sure I send along the token.
In my app (which I created on https://apps.dev.microsoft.com) I've set all Task related permissions and User.read for good measure. As for the platform I've set "Web".
Is there something I'm missing or mis-configuring?
My init method:
const self = this
this.userAgentApplication = new Msal.UserAgentApplication(this.clientID, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
this.userAgentApplication.loginPopup(['Tasks.readwrite']).then(function (token) {
let user = self.userAgentApplication.getUser()
if (user) {
self.token = token
localStorage.setItem('token', token)
self.getTasks()
}
}, function (error) {
console.log(error)
})
My getTasks method:
const bearer = 'Bearer ' + this.token
let headers = new Headers()
headers.append('Authorization', bearer)
let options = {
method: 'GET',
headers: headers
}
// Note that fetch API is not available in all browsers
fetch('https://outlook.office.com/api/v2.0/me/tasks', options).then(function (response) {
let contentType = response.headers.get('content-type')
if (response.status === 200 && contentType && contentType.indexOf('application/json') !== -1) {
response.json().then(function (data) {
console.log(data)
})
.catch(function (error) {
console.log(error)
})
} else {
response.json().then(function (data) {
console.log(data)
})
.catch(function (error) {
console.log(error)
})
}
})
.catch(function (error) {
console.log(error)
})
Your token is scoped for Graph, not Outlook. Tasks.readwrite will default to the Microsoft Graph and won't work against the Outlook endpoint.
Change this bit:
this.userAgentApplication.loginPopup(['Tasks.readwrite'])
To:
this.userAgentApplication.loginPopup(['https://outlook.office.com/Tasks.readwrite'])
You are trying to use Microsoft Graph, so the request should look like
GET https://graph.microsoft.com/beta/users/{id|userPrincipalName}/outlook/tasks
It's documented here:https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/outlookuser_list_tasks
I believe you got a Microsoft Graph token but you're trying to use it on the Outlook REST endpoint, which would not work.

Returning JSON within the same Fetch POST API call

I currently have a web app built on the Express framework for Node.js.
I'm now building a React Native app that needs to perform user login. To do this, I've created a mongo-helpers.js file that will simply perform the api calls to localhost:3000/api and return JSON. So far, I've had no problem making GET calls and receiving data, however here is my problem and what I want to do:
Objective: When the Login button is pressed (in the React Native app) make an API call to localhost:3000/api/login, passing the username and password into the body of the fetch API POST request. If the user is logged in, send back the user's Mongo Document.
Problem: Though I am able to make the API Post request and successfully log in the user, I am unable to send back the user's Mongo document with res.json(user)
Here is my code:
Express WEB APP: routes/api/api.js
// POST and login user
router.post('/login', (req, res) => {
user.findOne({name: req.body.name, password: req.body.password})
.then((user) => {
console.log(user);
res.json(user)
})
.catch((error) => res.json(error))
});
React Native App: mongo-helpers.js
// Returns a promise
// if login succeeds then return the user
// if login fails return error
exports.login = (name, password) => {
return new Promise(
(resolve, reject) => {
fetch(APP_SERVER_URL+'/login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({name, password})
}).then((response) => {
console.log('Response', response);
}).then((data) => {
console.log(data); // returns undefined :(
resolve(data);
}).catch((error) => {
console.log('Error', error); // no error is returned
reject(error);
})
}
)
};
I am logging the response and here is what it shows. There's no user data within it
You need to call the json function and it will returns a promise, try that:
fetch(APP_SERVER_URL+'/login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({name, password})
}).then((response) => {
response.json().then((data) => {
console.log(data)
});
}).catch((error) => {
console.log('Error', error); // no error is returned
reject(error);
})
}
https://developer.mozilla.org/en-US/docs/Web/API/Body/json
Use return response in first then callback.

Categories