I'm using Angular2 and rxjs.
I have an operation called login(). This will use a http.post request to send the authentication details to the server and will then receive a token back.
It needs to read the result and if the token is received successfully it will do some operations to validate the token and decode it, and if all of this is OK then it will send the username from the token to the server with a http.get and retrieve the user's details.
I would like all of the above to be returned as one Observable, but I'm scratching my head as to how two operations that should occur one after the other should be structured using the RxJS way.
I don't think subscribing to the first operation and then calling the second operation inside the first is the "right" way, because then how do you capture a failure in the first one.
Something like this?
this.http.post('http://localhost/auth/token', creds, {
headers: headers
})
.map(res => res.json())
.do(
// validate token
// decode token
)
.thenDo(
// get user details
this.http.get(url, options)
.map(res => res.json())
.do(
//save user and token in localStorage
)
)
i dont know much about Rxjs do and thenDo function but yes you can do like this
this.http.post('http://localhost/auth/token', creds, {
headers: headers
})
.map(res => {
return [{status: res.status , json: res.json()}]
})
.subscribe(res=>{
if(res[0].status == 200){ // do you action depends on status code you got assuming 200 for OK response
this.validateToken() // Validate your token here in some method named as validateToken
this.decodeToken() // decode token here in this method
this.getUserDetail() //if everything worked fine call your another get request in another method
}
},
err => {
console.log(err, err.status) //catch your error here
})
getUserDetail(){
// make http get request for user detail and saveing into locastroage
}
Using flatMap is a good way to chain operations that each return a new Promise or Observable. Each time we need to map over a function that returns a Promise or Observable, we can use flatMap to construct a stream that emits the resolved data. Here we construct an Observable of user data, and finally we can subscribe to it (to save to localstorage, etc).
I've assumed your validation code is just some function that returns a Promise or Observable.
const options = { headers };
const user$ = this.http.post('http://localhost/auth/token', creds, options)
.map(res => res.json())
.flatMap(validationFunctionThatReturnsAPromise)
.flatMap(authResponse => {
// get user details
return this.http.get(url, options).map(res => res.json());
});
user$.subscribe(user => /** do something with the user data **/);
Related
I'm calling an endpoint which needs an authentication on the server.
/users/:userId/my/endpoint => getData(): Observable<any>
The server responds with an error, if the authentication is expired. Then i'm able to refresh this authentication with a client secret i'm sending by POST
/refresh/me => refreshAuth(): Observable<any>
After that, i'll send the first request again and get a successful response (happy case).
How can i achieve this with a single Observable? I know, there is for example catchError and retry.
getData()
.pipe(
catchError(e => {
if (error.code === 1224) {
return refreshAuth();
}
return throwError(error);
}),
retry(1)
).subscribe();
But this would retry every error case, right? How can i retry the whole chain once on the specific auth expired case?
This is how it might be implemented:
getData()
.pipe(
retryWhen(error => error.pipe(
exhaustMap(e => (e.code == 1224) ? refreshAuth() : throwError(e))),
take(1)
)
)
.subscribe();
Is there way to avoid storing a result using closures from an initial HTTP request for use in a second HTTP request, as well as, in the subscribe block? The second request depends on the first so I can't use combineLatest, etc.
let acceptedOrgAgreement = false;
// Initial HTTP request
this.organizationResource
.getOrganizationById(organizationId)
.pipe(
// Initial HTTP response
map((organization: Organization) => !!organization.acceptedAgreementDate),
exhaustMap((hasSignedOrgAgreement: boolean) => {
// Would rather not have to temporarily store this in outer function scope
acceptedOrgAgreement = hasSignedOrgAgreement;
// Second HTTP request that relies on the initial request
return this.siteResource.updateSite(payload, hasSignedOrgAgreement);
}),
// Pass first and second HTTP responses to subscribe as tuple
map((response: any) => [response, acceptedOrgAgreement])
)
.subscribe(([response, hasSignedOrgAgreement]: [any, boolean]) => {
// Do some work using both responses
});
what about return a forkJoin ?
this.organizationResource
.getOrganizationById(organizationId)
.pipe(
map((organization: Organization) => !!organization.acceptedAgreementDate),
exhaustMap((hasSignedOrgAgreement: boolean) => {
return forkJoin([this.siteResource.updateSite(payload, hasSignedOrgAgreement), of(hasSignedOrgAgreement)]);
}),
)
.subscribe(([response, hasSignedOrgAgreement]: [any, boolean]) => {
});
I am creating new frontend for an interview system. Some its API endpoints is updated, so getting pagination info is not a problem, but old ones still have pagination data inside response headers.
P.S. we are using react, redux and redux-observable
RxJS has the following call:
ajax({ ...params }).pipe(
map(response => {
// here I need to somehow get headers from ajax response
}),
catchError(errorResponse => {
// return error
})
)
I've been looking for the same answer, looks like there is a way (See: https://stackblitz.com/edit/typescript-k2ggm2?file=index.ts):
ajax({ ...params }).pipe(
map(response => {
// here I need to somehow get headers from ajax response
console.log(response.xhr.getAllResponseHeaders())
console.log(response.xhr.getResponseHeader('pragma'))
}),
catchError(errorResponse => {
// return error
})
)
I am wondering if it is possible to do in fetch all the things you can do in traditional ajax?
Because I'm having a problem with a simple login authentication using express. I want to send a response like Login error if the username/password is incorrect, or to redirect the user to the homepage if both is correct, to the client without refreshing the page.
I understand that you can do this in AJAX, but is it possible to do it in fetch also?
I tried using express js and sending a response through a json, but I can't figure out how to handle the response without refreshing the page.
I tried doing it like this in the express server
//if valid
res.json({
isValid: true
})
//if invalid
res.json({
isValid: false
})
And in the client side, specifically in the login page, I have this javascript that handles the submitting of the information
fetch('https://localhost:3000/auth', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username,
password
})
})
.then(response => response.json())
.then(data => {
//I understand that in this part, you can handle the response, but the problem is, I don't know how.
}
})
.catch(console.log)
You are SO close! You've got the fetch, then you've parsed it with response.json, so the next thing is the .then(). In that, you have the JSON object being passed into a param you've named data. All you need to do is check if that has the isValid property!
fetch('https://localhost:3000/auth', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username,
password
})
})
.then(response => response.json())
.then(data => {
if(data.isValid){
// Do something with a valid user. Redirect or whatever.
} else {
// Here, isValid is not set, or is false.
// Send them packing!
}
}
})
.catch(err => console.error("I died: ", err) );
ALSO, take a look at the .catch() block -- in the event of an error, that catches an Error thrown by either the fetch(), or a then(). So you need to add a parameter for the error, and a function body to handle that. I've edited my code sample to demonstrate.
Won't actually run here, but it's formatted all pretty.
I have the following interceptor on my axios reponse :
window.axios.interceptors.response.use(
response => {
return response;
},
error => {
let errorResponse = error.response;
if (errorResponse.status === 401 && errorResponse.config && !errorResponse.config.__isRetryRequest) {
return this._getAuthToken()
.then(response => {
this.setToken(response.data.access_token, response.data.refresh_token);
errorResponse.config.__isRetryRequest = true;
errorResponse.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return window.axios(errorResponse.config);
}).catch(error => {
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
The _getAuthToken method is :
_getAuthToken() {
if (!this.authTokenRequest) {
this.authTokenRequest = window.axios.post('/api/refresh_token', {
'refresh_token': localStorage.getItem('refresh_token')
});
this.authTokenRequest.then(response => {
this.authTokenRequest = null;
}).catch(error => {
this.authTokenRequest = null;
});
}
return this.authTokenRequest;
}
The code is heavily inspired by https://github.com/axios/axios/issues/266#issuecomment-335420598.
Summary : when the user makes a call to the API and if his access_token has expired (a 401 code is returned by the API) the app calls the /api/refresh_token endpoint to get a new access_token. If the refresh_token is still valid when making this call, everything works fine : I get a new access_token and a new refresh_token and the initial API call requested by the user is made again and returned correctly.
The problem occurs when the refresh_token has also expired.
In that case, the call to /api/refresh_token returns a 401 and nothing happens. I tried several things but I'm unable to detect that in order to redirect the user to the login page of the app.
I found that in that case the if (!this.authTokenRequest) statement inside the _getAuthToken method returns a pending Promise that is never resolved. I don't understand why this is a Promise. In my opinion it should be null...
I'm a newbie with Promises so I may be missing something !
Thanks for any help !
EDIT :
I may have found a way much simpler to handle this : use axios.interceptors.response.eject() to disable the interceptor when I call the /api/refresh_token endpoint, and re-enable it after.
The code :
createAxiosResponseInterceptor() {
this.axiosResponseInterceptor = window.axios.interceptors.response.use(
response => {
return response;
},
error => {
let errorResponse = error.response;
if (errorResponse.status === 401) {
window.axios.interceptors.response.eject(this.axiosResponseInterceptor);
return window.axios.post('/api/refresh_token', {
'refresh_token': this._getToken('refresh_token')
}).then(response => {
this.setToken(response.data.access_token, response.data.refresh_token);
errorResponse.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
this.createAxiosResponseInterceptor();
return window.axios(errorResponse.config);
}).catch(error => {
this.destroyToken();
this.createAxiosResponseInterceptor();
this.router.push('/login');
return Promise.reject(error);
});
}
return Promise.reject(error);
}
);
},
Does it looks good or bad ? Any advice or comment appreciated.
Your last solution looks not bad. I would come up with the similar implementation as you if I were in the same situation.
I found that in that case the if (!this.authTokenRequest) statement inside the _getAuthToken method returns a pending Promise that is never resolved. I don't understand why this is a Promise. In my opinion it should be null...
That's because this.authTokenRequest in the code was just assigned the Promise created from window.axios.post. Promise is an object handling kind of lazy evaluation, so the process you implement in then is not executed until the Promise was resolved.
JavaScript provides us with Promise object as kind of asynchronous event handlers which enables us to implement process as then chain which is going to be executed in respond with the result of asynchronous result. HTTP requests are always inpredictable, because HTTP request sometimes consumes much more time we expect, and also sometimes not. Promise is always used when we use HTTP request in order to handle the asynchronous response of it with event handlers.
In ES2015 syntax, you can implement functions with async/await syntax to hanle Promise objects as it looks synchronous.