New to Node.js and am slowly piecing things together from a number of tutorials and other posts here on the site.
Presently, I am attempting to make an API request by means of the https module, and use a value returned (an authentication token) in the client for a lightweight example.
My current attempt is very simple - a client-side js function is called on a button press, which makes an Ajax call to a node server router.
The problem that I am having is that I cannot seem to retrieve data from the server at the client. This may be an issue of event-handling or some asynchronous behavior that I'm not fully understanding - the value I'm trying to retrieve can be seen in the login() function that I call. Specifically, I see this token in the data value that I am writing out to the console (see api.js) Does anything from my current approach stand out as glaringly wrong?
Thank you in advance for any help you might be able to offer.
For context, App.js is where I am storing this route, as well as starting the server. I am then making use of the api.js module by means of an ajax call from client.js.
//app.js
var express = require('express');
var app = express();
var api = require('./public/api.js');
app.post('/login', function(req, res) {
//token has no value currently, but along the lines of what I'm hoping to accomplish
var token = api.login();
res.end(token);
});
app.use(express.static(__dirname + '/public'));
var server = app.listen(9001, function() {
console.log('Listening on port 9001');
});
//api.js (node module)
var exports = module.exports = {};
var querystring = require('querystring');
var https = require('https');
var host = 'api.robinhood.com';
var username = '[username here]';
var password = '[password here]';
var response = null;
function performRequest(endpoint, method, data, success) {
var dataString = JSON.stringify(data);
var headers = {};
if (method == 'GET') {
endpoint += '?' + querystring.stringify(data);
}
else {
headers = {
'Content-Type': 'application/json',
'Content-Length': dataString.length
};
}
var options = {
host: host,
path: endpoint,
method: method,
headers: headers
};
var req = https.request(options, function(res) {
res.setEncoding('utf-8');
var responseString = '';
res.on('data', function(data) {
responseString += data;
});
res.on('end', function() {
var responseObject = JSON.parse(responseString);
success(responseObject);
});
});
req.write(dataString);
req.end();
}
exports.login = function() {
return performRequest('/api-token-auth/', 'POST', {
username: username,
password: password
}, function(data) {
sessionId = data.token;
console.log('Logged in:', sessionId);
});
}
<!-- index.html -->
<!DOCTYPE html>
<html>
<script type="text/javascript" src="jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="client.js"></script>
<head>
<title>Making API Calls!</title>
</head>
<body>
<button id="login">Log in</button>
<hidden id="token">Put an authentication token here</hidden>
</body>
</html>
//client.js
$(function(){
$('#login').click(function() {
$.ajax({
type: 'POST',
url: 'http://localhost:9001/login',
contentType: "application/json; charset=utf-8",
datatype: 'json',
success: function(result) {
console.log(result); // this is where I would consume/store the token
$('#login').html(':)');
},
error: function(result) {
console.log(status);
console.log(result);
}
});
});
});
Your api.login is not returning the token, it's returning what performRequest returns, undefined
Like you said it have something to do with the asynchronous behavior of login, it needs to return the token through a promise or a callback
example with a promise:
exports.login = function() {
return new Promise(function(resolve) {
performRequest('/api-token-auth/', 'POST', {
username: username,
password: password
}, function(data) {
sessionId = data.token;
console.log('Logged in:', sessionId);
resolve(sessionId);
});
}
});
}
app.post('/login', function(req, res) {
api.login()
.then(token => res.end(token))
.catch(err => res.end(err));;
});
Related
desired behaviour
allow user to download text to speech audio file by clicking a button, like this official demo:
https://text-to-speech-starter-kit.ng.bluemix.net
what i've tried
i am using:
https://github.com/watson-developer-cloud/node-sdk
i can generate an audio file server side but can't figure out how to send that file back to the client for them to save - so i am trying to generate it client side instead.
attempt 01: generate audio file server side
server.js (works)
const fs = require('fs');
const TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
const textToSpeech = new TextToSpeechV1({
iam_apikey: '{apikey}',
});
const synthesizeParams = {
text: 'Hello world',
accept: 'audio/wav',
voice: 'en-US_AllisonVoice',
};
textToSpeech.synthesize(synthesizeParams)
.then(audio => {
audio.pipe(fs.createWriteStream('hello_world.wav'));
})
.catch(err => {
console.log('error:', err);
});
for reference, according to the docs, the .synthesize() method response type is:
NodeJS.ReadableStream|FileObject|Buffer
attempt 02: generate audio file client side
server.js - required to get token (works)
var AuthorizationV1 = require('ibm-watson/authorization/v1');
var iam_apikey = local_settings.TEXT_TO_SPEECH_IAM_APIKEY;
var url = local_settings.TEXT_TO_SPEECH_URL;
var authorization = new AuthorizationV1({
iam_apikey: iam_apikey,
url: url
});
const api_tts_token_get = async (req, res) => {
authorization.getToken(function(err, token) {
if (!token) {
console.log('error:', err);
} else {
res.json({ token: token, url: url });
}
});
}
app.route("/api/:api_version/text-to-speech/token")
.get(api_tts_token_get);
client.js (doesn’t work)
var TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
const get_token = (parameters) => {
$.ajax({
url: "/api/v1/text-to-speech/token",
data: parameters,
dataType: 'json',
cache: false,
headers: headers,
success: function(results) {
var token = results.token;
var url = results.url;
var textToSpeech = new TextToSpeechV1({ token: token, url: url });
var synthesizeParams = {
text: 'hello world!',
accept: 'audio/wav',
voice: 'en-US_AllisonV3Voice'
};
textToSpeech.synthesize(synthesizeParams, function(err, result) {
if (err) {
return console.log(err);
}
console.log(result);
});
},
statusCode: {
500: function() {
console.log("that didn't work");
}
}
});
}
webpack.config.js
added per instructions at:
https://github.com/watson-developer-cloud/node-sdk/tree/master/examples/webpack#important-notes
node: {
// see http://webpack.github.io/docs/configuration.html#node
// and https://webpack.js.org/configuration/node/
fs: 'empty',
net: 'empty',
tls: 'empty'
},
chrome dev tools errors:
xhr.js:108 Refused to set unsafe header "User-Agent"
The provided value 'stream' is not a valid enum value of type XMLHttpRequestResponseType.
Access to XMLHttpRequest at 'https://***.watsonplatform.net/text-to-speech/api/v1/synthesize?voice=en-US_AllisonV3Voice'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Request header field x-ibmcloud-sdk-analytics is not allowed by
Access-Control-Allow-Headers in preflight response.
Error: Response not received. Body of error is HTTP ClientRequest object
at RequestWrapper.formatError (requestwrapper.js:218)
at eval (requestwrapper.js:206)
Here is one solution i have figured out.
It generates the audio file server side and sends it back via res.download().
The only caveat is that you can't use $.ajax() but rather something like:
window.open("/api/v1/audio?file_id=12345");
server.js
var TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
const api_audio_get = async (req, res) => {
var query_parameters = req.query;
var file_id = query_parameters.file_id;
var textToSpeech = new TextToSpeechV1({
iam_apikey: local_settings.TEXT_TO_SPEECH_IAM_APIKEY,
url: local_settings.TEXT_TO_SPEECH_URL
});
const synthesizeParams = {
text: 'here is test voice',
accept: 'audio/wav',
voice: 'en-US_AllisonV3Voice',
};
textToSpeech.synthesize(
synthesizeParams,
function(err, audio) {
if (err) {
console.log(err);
return;
}
// see: https://stackoverflow.com/a/46413467
// this allows you to create temp file on server, send it, then delete it
var filename = file_id + ".wav";
var absPath = path.join(__dirname, "/my_files/", filename);
var relPath = path.join("./my_files", filename); // path relative to server root
// see: https://nodejs.org/en/knowledge/advanced/streams/how-to-use-fs-create-write-stream/
var write_stream = fs.createWriteStream(relPath);
// audio is written to the writestream
audio.pipe(write_stream);
// see: https://stackoverflow.com/questions/19829379/detecting-the-end-of-a-writestream-in-node
write_stream.on('finish', function() {
res.download(absPath, (err) => {
if (err) {
console.log(err);
}
fs.unlink(relPath, (err) => {
if (err) {
console.log(err);
}
console.log("FILE [" + filename + "] REMOVED!");
});
});
});
}
);
}
// route handler
app.route("/api/:api_version/audio")
.get(api_audio_get);
client.js
$(document).on("click", ".download_audio", function() {
window.open("/api/v1/audio?file_id=12345");
});
I configured some routes :
var express = require('express');
var router = express.Router();
router.post('/isAccessible/:orgaId', function(req, res, next) {
res.send("------ param = "+orgaId);
});
module.exports = router;
Then inside an ejs file :
<script>
function isOrganisationAccessible(orgaId) {
$.ajax({
url: "/organisation/isAccessible/"+orgaId,
type: "POST",
dataType: "text",
success : function(data, status, xhr) {
return data;
},
error : function(xhr, status, error) {
return "";
}
});
}
$(document).ready(function() {
alert("test = "+isOrganisationAccessible("xxx"));
});
</script>
At runtime I get undefined ! So how to retrieve the parameter passed to the route ?
It looks like the issue you're having is that isOrganisationAccessible is asyncronous.
Returning the data from the success function is not having the result you're expecting
because the data is not returned from isOrganisationAccessible, only from the success
function. isOrganisationAccessible will always return undefined.
You can address this by using a promise:
function isOrganisationAccessible(orgaId) {
return new Promise((resolve, reject) => {
$.ajax({
url: "/organisation/isAccessible/"+orgaId,
type: "POST",
dataType: "text",
success : function(data, status, xhr) {
resolve(data);
},
error : function(xhr, status, error) {
reject(error);
}
});
})
}
Then resolve the promise to check the result:
$(document).ready(function() {
isOrganisationAccessible("xxx").then((data) => {
alert(data)
})
})
You cannot access orgaId directly. You need to access it like this :
req.params.orgaId
update:
I tested this simple app and it is working fine on /isAccessible/abc
I tested with the get query but with post also it should be fine. Also, why are you using post when you are not sending any data?
const express = require('express');
let app = express();
// Function to handle the root path
app.get('/isAccessible/:orgaId', function(req, res, next) {
res.send("------ param = "+req.params.orgaId);
});
let server = app.listen(3000, function() {
console.log('Server is listening on port 3000')
});
I have a two server(running on two different port), one is for chat application, and another is for API generation server(user should register by providing company
details,and my algorithm gives a API key to the user).
The problem is, i am checking the valid API key,provided by the user, if API key is true then it should redirect to chat server(port no 5200).
But it doesn't work, please give any idea to resolve this issues.
Here is my code,
`
app.post('/checkAPIkey',function(req,res){
var apikey=req.query.apikey;
var apikey1=uuidAPIkey.isAPIKey(apikey);
if(apikey1){
res.writeHead(302, {
Location: 'http://localhost:5200'
});
}else{
res.end("error");
}
});`
What you need is called Request Forwarding.
Example:
const http = require('http');
app.post('/checkAPIkey', function(req,res){
var apikey=req.query.apikey;
var apikey1 = uuidAPIkey.isAPIKey(apikey);
if(apikey1){
const options = {
port: NEW_PORT,
hostname: 'NEW_HOST',
method: 'POST',
path: '/'
};
var reqForward = http.request(options, (newResponse) => {
//Do something with your newResponse
var responseData = "";
newResponse.on('data', (chunk) => {
//Add data response from newResponse
responseData += chunk;
});
newResponse.on('end', () => {
//Nothing more, send it with your original Response
response.send(responseData);
});
});
// If ERROR
reqForward.on('error', (e) => {
console.error('Error: ' + e);
});
// Write to the request
reqForward.write(YOUR_POST_DATA);
reqForward.end();
} else {
res.end("error");
}
});
I have a problem with my little Node.js test setup. I basically want an endpoint that I can call, and this endpoint can call different other endpoints and give me a JSON as a response. When I have a look at the console output of the performRequest function, everything looks good. But the return of this function doesn't get passed. I always get an empty {} as a response.
The routes.js that holds my routes:
var s24 = require("./s24");
var routes = function(app) {
app.get("/ping", function(req, res) {
res.send("<p>pong</p>");
console.log("Received GET");
});
app.get("/getCategories", function(req, res) {
var output = s24.getCategories();
res.type('application/json');
return res.send(output);
});
};
module.exports = routes;
The s24.js that queries another REST-API:
var functions = require('./functions');
var appID = "XYZ";
var authorization = "ABC";
var getCategories = function () {
var output = functions.performRequest("https://api.s24.com/v3/"+appID+"/categories", authorization);
console.log(output);
return output;
};
module.exports.getCategories = getCategories;
The functions.js that holds all my relevant functions:
var unirest = require('unirest');
var performRequest = function(endpoint,authorization,body) {
unirest.get(endpoint)
.headers({'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': authorization})
.send(body)
.end(function (response) {
var data = response.body;
console.log(data);
return data;
});
};
module.exports.performRequest = performRequest;
performRequest is an asynchronous function usually in node.js you can't just return the response of an asynchronous function. You can use async await feature with babel but the simplest way is just to use callbacks or promises.
Your code should look something as following:
var performRequest = function (endpoint, authorization, body, callback) {
unirest.get(endpoint)
.headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': authorization
})
.send(body)
.end(function (err, response) {
var data = response.body;
callback(data["id"]);
});
}
var getCategories = function () {
var output = functions.performRequest(
"https://api.s24.com/v3/" + appID + "/categories",
authorization,
function (output) {
console.log(output);
});
};
As you can see passing the response from one callback to an other is seems ugly so I suggest you to rethink your design.
Using the Instagram API i am calling the endpoint.
https://api.instagram.com/v1/tags/MYTAG/media/recent?access_token=MYACCESSTOKEN
I am getting different results returned when calling from my app vs calling from apigee.com. The difference being the access_token.
When i use https://apigee.com/console/instagram and make the instagram api call i get back 8 images. (after logging in with my instagram creditials), which is what i would expect.
if i log in with the same creditials using my nodejs app , i get a different auth token(which you would assume), but i get an empty data set back.
{ pagination: { next_min_id: 'STUFF', min_tag_id: 'STUFF' },
meta: { code: 200 },
data: []
}
I must be missing something simple.
Can anyone see what i may be doing wrong in my nodejs code
Thanks for any help
This is the nodejs code:::
var https = require('https');
var http = require('http');
var express = require('express');
var app = express();
var fs = require("fs");
var client_id = "CLIENT_ID";
var client_secret = "CLIENT_SECRET";
var redirect_uri = 'https://MYDOMAIN/handleauth';
var authorize_link = 'https://api.instagram.com/oauth/authorize/?client_id=' + client_id + '&redirect_uri=' + redirect_uri + '&response_type=code';
app.get('/authorize_user', function (req, res) {
res.redirect(authorize_link);
});
app.get('/handleauth', function (req, res) {
res.send("ok");
if (req.query['code']) {
var request = require('request');
var post_data = {
'client_id': client_id,
'client_secret': client_secret,
'grant_type': 'authorization_code',
'redirect_uri': redirect_uri,
'code': req.query['code']
};
var headers = {
'User-Agent': 'Super Agent/0.0.1',
'Content-Type': 'application/x-www-form-urlencoded'
}
var post_options = {
url: 'https://api.instagram.com/oauth/access_token',
method: 'POST',
headers: headers,
form: post_data
};
request(post_options, function (error, response, body) {
if (error || response.statusCode != 200) {
console.error(error);
} else {
var pbody = JSON.parse(body);
console.log('Response: ' + pbody);
console.log('pbody.access_token: ' + pbody.access_token);
var options = {
url: 'https://api.instagram.com/v1/tags/MYTAG/media/recent?access_token='+pbody.access_token,
method: 'GET'
};
request(options, function (error, response, body) {
if (error && response.statusCode != 200) {
console.error(error);
}else{
var jsonobjArr = JSON.parse(body);
console.log(jsonobjArr);
}
});
}
});
}
});
var options = {
key: fs.readFileSync("/ssl_certs/me.key"),
cert: fs.readFileSync("/ssl_certs/me_bundle.crt")
};
https.createServer(options, app).listen(4000, function () {
console.log("HTTPS Express Instagram server listening on port " + 4000);
});
You're in Sandbox Mode.
Data is restricted to the 10 users and the 20 most recent media from each of those users.
I realized this because I could only receive data from the tags I've posted.
Your question helped me figure out how to generate the access token. I couldn't get node-rest-client to work. Thanks!
My suggestion is to build the best application you can to mitigate the approval process.