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.
Related
I am trying to write a custom Cypress command that sends a POST request to an endpoint, & I then want to store the response body in my test.
Here is what the response body looks like in Postman:
Here is my custom command in cypress/support/commands.js, for simplicity, I've removed the request body values:
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: `POST`,
url: `myUrl`,
body: {}
}).then((resp) => {
return resp
});
});
Here is the code in my spec file:
let response = cy.createStudent(email);
cy.log(response)
However, when I run the code I get back the below object rather than the response body:
Can someone please tell me where I am going wrong, & what changes are required to return the actual HTTP response body?
If you look at the console message, there's a type $Chainer shown which is a wrapper object around the result you actually want (response).
The Chainer is fundamental to Cypress being able to retry queries that fail initially but may succeed within a timeout period (usually 4 seconds).
But it means you can't use the return value. Instead you need to "unwrap" the value using .then().
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: 'POST',
url: 'myUrl',
body: {...}
})
// The response is put on the "chain" upon exit of the custom command
// You need nothing else here to get the raw response
})
cy.createStudent().then(response => {
cy.log(response)
});
You can add a step to extract details from the response, like
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: 'POST',
url: 'myUrl',
body: {...}
})
.then(response => {
expect(response.success).to.eq(true) // check expected response is good
return response.body.id // pass on just the id
})
})
cy.createStudent().then(id => {
cy.log(id)
});
If you'll only ever be using the value in a Cypress chain, you could simply alias the command.
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: `POST`,
url: `myUrl`,
body: {}
}).as('student');
});
...
cy.createStudent();
cy.get('#student').then((response) => {
cy.log(response.body) // assuming you'd want to log the response body.
});
// OR
cy.get('#student').its('body').should('eq', { foo: 'bar' });
// the above example doesn't work with logging, but I'm guessing you don't _just_ want to log the response
If you may need the variable at other times outside of a Cypress chain, you could always stash the variable in Cypress.env().
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: `POST`,
url: `myUrl`,
body: {}
}).then((res) => {
Cypress.env('student', res);
});
});
...
cy.createStudent().then(() => {
cy.get('foo').should('have.text', Cypress.env('student').body.foo);
});
// key point is referencing the entire response by `Cypress.env('student')`
I am using spring security + thymeleaf to recognise my css and javascript files. These css and javascript files make up my front end login page, and for special reasons, my login form is within a js file and i include this file in my main html file. To authenticate the login credentials, a custom /POST request needs to be called using javascript like below. This function below executes when the form submit button is pressed.
function submitForm() {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
var urlencoded = new URLSearchParams();
urlencoded.append("username", "<removed>");
urlencoded.append("password", "<removed>");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: urlencoded,
redirect: 'follow'
};
fetch("http://localhost:8080/userAuth", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
}
Controller to handle successful login end point
#Controller
public class LoginSuccessController {
#GetMapping("/login_success")
public String success() {
return "success";
}
}
On the browser, I can infer that authentication is successful and there is something that happens related to my successful login endpoint. However, my browser remains at localhost:8080/login and does not redirect to the login_success endpoint. I am unable to figure out why so. Any help is much appreciated.
Browser network activity after submit button
because you use ajax. if you check networks tab I believe you will see response ans 301/302 with Location header.
Edit: you have to either use plain html form or redirect manually from js side
Edit 2:
fetch("http://localhost:8080/userAuth", requestOptions)
.then(response => response.text())
.then(result => {
// do some stuff with response
location.href = '/my-success-page-after-login'; // <- here is redirect
})
.catch(error => console.log('error', error));
Explanation: the problem is that browser doesn't follow redirect response for AJAX calls. Ajax call will just return you a response from server(redirect with 301/302 status code in your case) but this will NOT make browser to follow that redirect
I am new to rxjs and want to know how to handle this use case.
This is axios promise, how to convert it so that it uses rxjs's ajax operator
export const onLogin = ({ username, password }) =>
axios({
method: "post",
url: O_TOKEN_URL,
data: querystring.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: "password",
username,
password
}),
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
This is my action,
export const onSubmit = payload => ({
type: FETCH_USER,
payload // payload contains username & password
});
This is my epic for now,
export const loginEpic = action$ =>
action$
.ofType(FETCH_USER)
// somehow import onLogin from above and resolve it
// then, dispatch FETCH_USER_FULFILLED
.do(
payload => console.log(payload.username, payload.password)
// i am able to console these username and password
)
.mapTo(() => null);
I want to resolve onLogin function somehow, when FETCH_USER is dispatched, using rxjs's ajax operator.
And, I want onLogin function, which returns promise/observable, to be set up in different file so that I can keep track of all the ajax requests
These are the packages,
"redux-observable": "^0.18.0",
"rxjs": "^5.5.10",
Could you also point me to a documentation that covers this and various use case for post, put ... requests? I couldn't find any.
The ajax config object is fairly similar to what you already have. I'm assuming the data property for the axios request is the request body.
import {ajax} from 'rxjs/observable/dom/ajax';
export const onLogin = ({ username, password }) =>
ajax({
method: "POST",
url: O_TOKEN_URL,
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: "password",
username,
password
}),
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
Your epic would look something like this:
export const loginEpic = action$ =>
action$
.ofType(FETCH_USER)
.mergeMap(action =>
onLogin(action.payload)
// map will be called when the request succeeded
// so we dispatch the success action here.
.map((ajaxResponse) => fetchUserFulfilled())
// catch will be called if the request failed
// so we dispatch the error action here.
// Note that you must return an observable from this function.
// For more advanced cases, you can also apply retry logic here.
.catch((ajaxError, source$) => Observable.of(fetchUserFailed()))
);
Where fetchUserFulfilled and fetchUserFailed are action creator functions.
There does not seem to be much documentation of the RxJS 5 ajax method yet. Here are the links to the official v5 docs for the AjaxRequest, AjaxResponse and AjaxError. The AjaxError object in particular has 0 information so far (at the time of this answer) so you will need to rely on the source code if you need to use this object for more than a trigger to tell the user that something went wrong. The ajax source code is here.
I send a request to a remote API. It takes a little time for API to proceed on its side.
After this little waiting time, i can see in network tab a HTTP 200. In the response, I got the proper intended information. Everything on the API side works fine.
BIT on the console, I can see I encountered a XMLHttpRequest Error.
Why, especially if I have a XMLHttpRequest Error, the POST is completed with 200? Shouldn't it be "blocked" by Angular2?
The unintended result is: my file is correctly uploaded and handled by the API, but in Angular2, it triggers the ERROR part of my call.
If I use https://resttesttest.com/ for example, it seems to encounter the same error but it doesn't finalize the POST:
Oh no! Javascript returned an
HTTP 0 error. One common reason this might happen is that you
requested a cross-domain resource from a server that did not include
the appropriate CORS headers in the response.
Angular 2 Code for this call
this.http
.post(this.documentUploadAPIUrl, formData, options)
.subscribe(
res => {
this.responseData = res.json();
console.log(this.responseData);
console.log('Uploaded a blob or file!');
},
error => {
console.log('Upload failed! Error:', error);
}
);
try to set withCredential attribute of xmlHttpRequest to true, this will send credentials managed by the browser, in angular 2 you can do like this
import { RequestOptions } from '#angular/http';
this.http
.post(this.documentUploadAPIUrl, formData, this.post_options)
.subscribe(
res => {
this.responseData = res.json();
console.log(this.responseData);
console.log('Uploaded a blob or file!');
},
error => {
console.log('Upload failed! Error:', error);
}
);
post_options() {
return new RequestOptions({ method: 'post', withCredentials : true });
}
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 **/);