How do I format a request.get using an XMLHttpRequest example - javascript

Goal
I would like to use the npm package request to get data from an API endpoint. The example I am following uses XMLHttpRequest() to get the data.
Question
How do I convert the XMLHttpRequest() to a request.get
Example Code
The OnSIP example I am following provides the following:
cURL example:
curl -X POST \
--data \
'Action=SessionCreate&Username=john.doe%40example.onsip.com&Password=mysuperpassword' \
https://api.onsip.com/api
XMLHttpRequest() example:
var data = new FormData();
data.append('Action', 'SessionCreate');
data.append('Username', 'john.doe#example.onsip.com');
data.append('Password', 'mysuperpassword');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.onsip.com/api', true);
xhr.onload = function () {
console.log(this.responseText);
}
xhr.send(data);
What I Tried
cURL
When I put my credentials into the cURL command, I have success, and the response indicates <IsValid>true</IsValid>
.
node.js
I took the cURL example and used this cURL to Node.js tool to get started.
// Config Settings
const onsipAction = "SessionCreate";
const onsipEmail = encodeURIComponent(onsipConfig.email);
const onsipPassword = onsipConfig.password;
const dataString = "Action=" + onsipAction +
"&Username=" + onsipEmail +
"&Password=" + onsipPassword;
console.log("dataString :", dataString);
const onsipSessionCreateOptions = {
url: "https://api.onsip.com/api",
method: "POST",
body: dataString
};
exports.getOnsipSessionId = function (request){
return (new Promise((resolve, reject) => {
request.get(onsipSessionCreateOptions, function (err, _resp, body) {
if (err) reject(err);
else {
console.log("body :", body);
resolve(body);
}
});
}).catch(err => console.log("err:", err)));
};
Logs
I see this error in the body, but not sure what it means.
Accessor parameter is required, but none was specified.
datastring: Action=SessionCreate&Username=fakename%40jahnelgroup.onsip.com&Password=fakepass
and this is the body:
<?xml version="1.0" encoding="UTF-8"?>
<Response
xmlns="http://www.jnctn.net/ns/rest/2006-01">
<Context>
<Action>
<IsCompleted>false</IsCompleted>
</Action>
<Request>
<IsValid>false</IsValid>
<DateTime>2019-02-06T15:18:10+00:00</DateTime>
<Duration>1</Duration>
<Errors>
<Error>
<Parameter>Action</Parameter>
<Code>Accessor.Required</Code>
<Message>Accessor parameter is required, but none was specified.</Message>
</Error>
</Errors>
</Request>
<Session>
<IsEstablished>false</IsEstablished>
</Session>
</Context>
</Response>

The Issue
As Mo A shows in his answer, I missed two things:
request.get is wrong, instead request.post is correct.
The OnSIP endpoint is ready for formData
The code that works for me
// Config Settings
const onsipAction = "SessionCreate";
const onsipEmail = onsipConfig.email;
const onsipPassword = onsipConfig.password;
const options = { method: "POST",
url: "https://api.onsip.com/api",
headers:
{ "content-type": "multipart/form-data;" },
formData:
{ Action: onsipAction,
Username: onsipEmail,
Password: onsipPassword,
Output: "json"
}
};
exports.getOnsipSessionId = function (request){
return (new Promise((resolve, reject) => {
request.post(options, function (err, response, body) {
if (err) reject(err);
else {
console.log("body :", body);
resolve(body); // Contains SessionId
}
});
}).catch(err => console.log("err:", err)));
};
Thanks, Mo A, OnSIP Devs, and MShirk for the support!

Your request appears to be a POST, rather than a GET.
Try the following snippet to recreate your XMLHttpRequest using Node:
var request = require("request");
var options = { method: 'POST',
url: 'https://api.onsip.com/api',
headers:
{ 'content-type': 'multipart/form-data;' },
formData:
{ Action: 'SessionCreate',
Username: 'john.doe#example.onsip.com',
Password: 'mysuperpassword' } };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
It's basic (doesn't include email encoding for instance), but should in theory work.

Related

Getting "No file" or empty file when trying to download PDF file with Node.js

The issue:
I need to download a PDF file from my server but getting either "No file" or empty file
Details:
Here is my server-side code:
let fileBuffered = '';
// authentication for downloading a file from Dropbox API to my server
const dropbox = dropboxV2Api.authenticate({
token: process.env.DEV_DROPBOX_SECRET_KEY
});
// configuring parameters
const params = Object.freeze({
resource: "files/download",
parameters: {
path: `/${customerFileFolder}/${fileName}`
}
});
let dropboxPromise = new Promise(function (resolve, reject) {
dropbox(params, function (err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
}).on('data',function(data) {
fileBuffered += data;
})
const file = fileBuffered;
res.setHeader("Content-Type", "application/octet-stream");
res.send(file);
The PDF file's that I'm trying to download size is 139,694 bytes. The length of the fileBuffered variable is 132,597. Here is the content of the variable as it is shown in the debugger:
Seems like a legit PDF file
Here is the client-side
function documentFileDownload(fileName) {
const ip = location.host;
let request = $.ajax({
type: "POST",
url: `${http() + ip}/documentFileDownload`,
headers: {
"Accept": "application/octet-stream"
},
data: {
fileName: fileName
},
error: function (err) {
console.log("ERROR: " + err);
}
});
console.log(request);
return request;
}
Problem:
Then I get the response on a client-side it looks like this:
Note the size of the responseText: 254Kb.
What I actually get in the browser is a "Failed - No file" message
What else I tried:
I tried to play with different Content-Types (application/pdf, text/pdf) on a server-side and tried to convert the variable to base64 buffer
const file = `data:application/pdf;base64, ${Buffer.from(fileBuffered).toString("base64")}`;
and added res.setHeader("Accept-Encoding", "base64");
but still getting the same result.
Any ideas?
I found a solution. I missed a .on("end", ) event while reading data from Dropbox stream. Here is a working solution:
Here is the server-side:
let chunk = [];
let fileBuffered = '';
// authentication for downloading a file from Dropbox API to my server
const dropbox = dropboxV2Api.authenticate({
token: process.env.DEV_DROPBOX_SECRET_KEY
});
// configuring parameters
const params = Object.freeze({
resource: "files/download",
parameters: {
path: `/${customerFileFolder}/${fileName}`
}
});
let dropboxPromise = new Promise(function (resolve, reject) {
dropbox(params, function (err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
}).on('data',function(data) {
fileBuffered += data;
}).on('end', () => {
// console.log("finish");\
// generate buffer
fileBuffered = Buffer.concat(chunk);
});
const file = `data:application/pdf;base64, ${Buffer.from(fileBuffered).toString("base64")}`;
res.setHeader("Content-Type", "application/pdf");
res.send(file);
Client-side:
function documentFileDownload(fileName) {
const ip = location.host;
let request = $.ajax({
type: "POST",
url: `${http() + ip}/documentFileDownload`,
responseType: "arraybuffer",
headers: {
"Accept": "application/pdf"
},
data: {
fileName: fileName
},
error: function (err) {
console.log("ERROR: " + err);
}
});
// console.log(request);
return request;
}
Try adding dataType: "blob" in your $.ajax method
and within the headers object add this 'Content-Type', 'application/json'.

How would I resolve this 422 error during POST?

I'm trying to follow a code along on Udemy except making my version of the project. After using bcrypt and jwt, I'm unable to make any posts to my app. I'm able to login fine, but when trying to post something, I get a 422 error.
This is what's getting me stuck.
const twottSubmitHandler = async (event) => {
event.preventDefault();
try {
const formData = new FormData();
formData.append("title", formState.inputs.title.value);
formData.append("description", formState.inputs.description.value);
formData.append("creator", auth.userId);
await sendRequest("http://localhost:3001/api/twotts", "POST", formData, {
Authorization: "Bearer " + auth.token,
});
history.push("/");
} catch (err) {}
};
In the twotts-controller, this is what's throwing the error,
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(new HttpError("Invalid input passed", 422));
}
const { title, description, creator } = req.body;
const createdTwott = new Twott({
title,
description,
creator,
});
Is there a way to make it so it adds it using JSON.stringify instead of using FormData? Using
try {
await sendRequest(
"http://localhost:3001/api/twotts",
"POST",
JSON.stringify({
title: formState.inputs.title.value,
description: formState.inputs.description.value,
creator: auth.userId,
}),
{ Authorization: "Bearer " + auth.token },
{ "Content-Type": "application/json" }
);
history.push("/");
} catch (err) {}
I still get invalid inputs. If I put the Authorization argument anywhere else, I'd get authentication issues.
the link to the repo is https://github.com/preintercede/Twotter (with commits) in case there's a part that I missed.
You're constructing FormData and send it (implicitly) with Content-Type: multipart/form-data. express-validator doesn't validate such data out-of-the box (reasoning: https://github.com/express-validator/express-validator/issues/276)
One solution would be to use https://github.com/expressjs/multer, but my recommendation is just to submit JSON data with correct Content-Type, as already you're doing for other requests:
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
modified: src/twotts/pages/NewTwott.js
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
# src/twotts/pages/NewTwott.js:39 # const NewTwott = () => {
event.preventDefault();
try {
- const formData = new FormData();
- formData.append("title", formState.inputs.title.value);
- formData.append("description", formState.inputs.description.value);
- formData.append("creator", auth.userId);
- await sendRequest("http://localhost:3001/api/twotts", "POST", formData, {
+ await sendRequest("http://localhost:3001/api/twotts", "POST", JSON.stringify({
+ title: formState.inputs.title.value,
+ description: formState.inputs.description.value,
+ creator: auth.userId
+ }), {
Authorization: "Bearer " + auth.token,
+ "Content-Type": "application/json",
});
history.push("/");
} catch (err) {}
I recommend to wrap this into a helper function. This could also set the Bearer token to the request headers if present.

Postman post request works but ajax post does not. Have checked client side js over and over

first question ever on stackoverflow and boy do I need an answer. My problem is that I have an endpoint to create an item, and it works when I send a POST request with Postman. I'm using node and express:
router.post("/", jwtAuth, (req, res) => {
console.log(req.body);
const requiredFields = ["date", "time", "task", "notes"];
requiredFields.forEach(field => {
if (!(field in req.body)) {
const message = `Missing \`${field}\` in request body`;
console.error(message);
return res.status(400).send(message);
}
});
Task.create({
userId: req.user.id,
date: req.body.date,
time: req.body.time,
task: req.body.task,
notes: req.body.notes
})
.then(task => res.status(201).json(task.serialize()))
.catch(err => {
console.error(err);
res.status(500).json({ message: "Internal server error" });
});
});
That endpoint works when I send with Postman and the req body logged with the right values.
But when I send my ajax request, my server code logs the req.body as an empty object ('{}'). Because Postman works I believe the problem is with my client side javascript but I just cannot find the problem. I and others have looked over it a million times but just can't find the problem. Here is my client side javascript:
//User submits a new task after timer has run
function handleTaskSubmit() {
$(".submit-task").click(event => {
console.log("test");
const date = $(".new-task-date").text();
const taskTime = $(".new-task-time").text();
const task = $(".popup-title").text();
const notes = $("#task-notes").val();
$(".task-notes-form").submit(event => {
event.preventDefault();
postNewTask(date, taskTime, task, notes);
});
});
}
function postNewTask(date, taskTime, task, notes) {
const data = JSON.stringify({
date: date,
time: taskTime,
task: task,
notes: notes
});
//Here I log all the data. The data object and all its key are defined
console.log(data);
console.log(date);
console.log(taskTime);
console.log(task);
console.log(notes);
const token = localStorage.getItem("token");
const settings = {
url: "http://localhost:8080/tasks",
type: "POST",
dataType: "json",
data: data,
contentType: "application/json, charset=utf-8",
headers: {
Authorization: `Bearer ${token}`
},
success: function() {
console.log("Now we are cooking with gas");
},
error: function(err) {
console.log(err);
}
};
$.ajax(settings);
}
handleTaskSubmit();
What I would do:
Change header 'application/json' to 'application/x-www-form-urlencoded' since official docs have no info on former one.
Stop using $.ajax and get comfortable with XHR requests, since jquery from CDN is sometimes a mess when CDN get's laggy and XHR is a native implement and available immediately. Yes it's a code mess, but you always know that it is not the inner library logic thing, but your own problem. You blindly use library, that conceals XHR inside and you do not know how to ask the right question "XHR post method docs" because you are not yet comfortable with basic technology underneath.
Save this and import the variable
var httpClient = {
get: function( url, data, callback ) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
var readyState = xhr.readyState;
if (readyState == 4) {
callback(xhr);
}
};
var queryString = '';
if (typeof data === 'object') {
for (var propertyName in data) {
queryString += (queryString.length === 0 ? '' : '&') + propertyName + '=' + encodeURIComponent(data[propertyName]);
}
}
if (queryString.length !== 0) {
url += (url.indexOf('?') === -1 ? '?' : '&') + queryString;
}
xhr.open('GET', url, true);
xhr.withCredentials = true;
xhr.send(null);
},
post: function(url, data, callback ) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
var readyState = xhr.readyState;
if (readyState == 4) {
callback(xhr);
}
};
var queryString='';
if (typeof data === 'object') {
for (var propertyName in data) {
queryString += (queryString.length === 0 ? '' : '&') + propertyName + '=' + encodeURIComponent(data[propertyName]);
}
} else {
queryString=data
}
xhr.open('POST', url, true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(queryString);
}
};
usage is as simple as jquery: httpClient.post(Url, data, (xhr) => {})
Check if you have body parser set-up in app.js
var bodyParser = require('body-parser');
app.use(bodyParser.json()); // get information from html forms
app.use(bodyParser.urlencoded({ extended: true })); // get information from html forms
if body parser is set-up try changing header to 'multipart/form-data' or
'text/plain'.
For just the sake check req.query
Cheers! :)

Testing an AJAX function with xhr-mock fails

I'm trying to test the following function from my network.js:
export function post (data) {
return new Promise(function (resolve, reject) {
// need to log to the root
var url = window.location.protocol + '//' + window.location.hostname
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 204) {
resolve(null)
} else {
reject(new Error('an error ocurred whilst sending the request'))
}
}
}
xhr.open('POST', url, true)
xhr.setRequestHeader('Content-type', 'application/json')
xhr.send(JSON.stringify(data))
})
}
My test case looks like this:
import xhrMock from 'xhr-mock'
import * as network from '../src/network'
describe('Payload networking test suite', function () {
beforeEach(() => xhrMock.setup())
afterEach(() => xhrMock.teardown())
test('POSTs JSON string', async () => {
expect.assertions(1)
xhrMock.post(window.location.protocol + '//' + window.location.hostname, (req, res) => {
expect(req.header('Content-Type')).toEqual('application/json')
return res.status(204)
})
await network.post({})
})
})
When running my test suite I'm getting:
xhr-mock: No handler returned a response for the request.
POST http://localhost/ HTTP/1.1
content-type: application/json
{}
This is mostly based on the documentation and I don't understand why its failing
Solution
add a trailing / to the url you are giving xhrMock.post()
Error Details
The url is http://localhost.
That turns into a req.url() of
{
protocol: 'http',
host: 'localhost',
path: '/',
query: {}
}
Calling toString() on that object returns 'http://localhost/'
xhr-mock compares the URLs by doing req.url().toString() === url
'http://localhost/' === 'http://localhost' returns false so xhr-mock is returning an error that no handler returned a response.
I found I had some problems as well and using the following module was a better alternative for me:
https://github.com/berniegp/mock-xmlhttprequest
Usage is pretty straight forward:
const MockXMLHttpRequest = require('mock-xmlhttprequest');
const MockXhr = MockXMLHttpRequest.newMockXhr();
// Mock JSON response
MockXhr.onSend = (xhr) => {
const responseHeaders = { 'Content-Type': 'application/json' };
const response = '{ "message": "Success!" }';
xhr.respond(200, responseHeaders, response);
};
// Install in the global context so "new XMLHttpRequest()" uses the XMLHttpRequest mock
global.XMLHttpRequest = MockXhr;

Creating an Asana Task using a POST http request

I'm trying to use the asana-api to create a Task using a POST http request but I keep getting a 400 bad request as a response.
I managed to get data from the Asana-api using ( a GET request ), but I'm having trouble sending data to Asana with ( a POST request )
I'm using the 'request' module to do the api call
here's the error message :
`{"errors":[{
"message":"Could not parse request data,invalid JSON",
"help":"For more information on API status codes and how to handle them,
read the docs on errors: https://asana.com/developers/documentation/getting-started/errors"}
]}`
Here's my code:
testTask(){
var taskName = "Test Name for a Test Task"
var workspaceID = "123456789"
var projectID = "123456789"
var assigneeID = "123456789"
var parentID = null
this.createTask(taskName, workspaceID, projectID, assigneeID, parentID)
}
createTask(taskName, workspaceID, projectID, assigneeID, parentID){
var token = "0/1234abcd5678efgh9102ijk"
var bearerToken = "Bearer " + token
var task = {
data: {
assignee: "me",
notes: "test test test test",
workspace: workspaceID,
name: taskName,
projects: [projectID],
parent: parentID
}
}
var options = {
"method" : "POST",
"headers" : {"Authorization": bearerToken},
"contentType": "application/json",
"payload" : JSON.stringify(task)
}
try {
var url = "https://app.asana.com/api/1.0/tasks";
request.post(url, options, function optionalCallback(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
}
catch (e) {
console.log(e);
}
}
I also tried a different implementation :
createTask(){
var token = "0/1234abcd5678efgh9102ijk"
var bearerToken = "Bearer " + token
var options = {
"method" : "POST",
"headers" : {"Authorization": bearerToken},
}
try {
request.post("https://app.asana.com/api/1.0/tasks?workspace=1234567&projects=765534432&parent=null&name=taskName&assignee=me", options, function optionalCallback(err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
}
catch (e) {
console.log(e);
}
}
Based on the examples provided by the request module, it appears that your options object uses payload as a key, but it should be body.

Categories

Resources