Access the response of Node.JS Post Request - javascript

I'm trying the wrap my head around the Client Credentials Flow of Spotify API and in their documentation they have this way to get the Access Token:
var client_id = 'CLIENT_ID';
var client_secret = 'CLIENT_SECRET';
const authOptions = {
url: "https://accounts.spotify.com/api/token",
headers: {
"Authorization":
"Basic " +
new Buffer.from(clientID + ":" + clientSecret).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
form: {
grant_type: "client_credentials",
},
json: true,
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var token = body.access_token;
}
});
Now I'm trying to get that token and export or use it in the API calls but whatever I do, I cannot access that statement.
Putting the POST into a variable or function and calling it results in undefined.
This is what I'm trying to achieve:
import authOptions from "./credentials.js";
import pkg from "request";
const { post } = pkg;
const Spotify = {
getAccessToken() {
post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
const token = body.access_token;
return token;
}
});
},
async search(input) {
const accessToken = Spotify.getAccessToken();
const response = await fetch(
`https://api.spotify.com/v1/search?q=${input}&type=artist`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
}
);
const data = await response.json();
console.log(data);
},
};
export default Spotify;
Yet of course there's no Access Token returned from that post request.
Is there any way I can convert that piece of code into Async/Await?

You can create a new promise:
async function getAccessToken(){
return new Promise((resolve, reject) => {
post(authOptions, function (error, response, body) {
if(error){
reject(error);
} else if (response.statusCode === 200) {
const token = body.access_token;
resolve(token);
}
});
});
};
The resolve/reject allows you to return that value when the callback is called, and it passes the error to the caller to handle. And you can use this format to promisify any callback-based function, by calling it inside of a promise and using resolve/reject to return the value.

Related

SWR not working properly with async fetch

Recently updated SWR - now for some reason my data is not fetching properly.
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
[`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
apiRequest
);
Using this fetching,
import firebase from "./firebase";
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
console.log("error", response);
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
// Check if a indexDb database exists
export function indexedDbdatabaseExists(dbname, callback) {
const req = window.indexedDB.open(dbname);
let existed = true;
req.onsuccess = function () {
req.result.close();
if (!existed) window.indexedDB.deleteDatabase(dbname);
callback(existed);
};
req.onupgradeneeded = function () {
existed = false;
callback(existed);
};
}
Now I'm looking at this StackOverflow thread,
useSWR doesn't work with async fetcher function
And thinking I'll just remake the fetcher to be without Async. I'm just wondering why this has stopped working though in general, and if I can just keep my existing codebase.
The error is a 400 message, it only happens with this expressions API call which takes longer to load due to the amount of data I think,
xxxx/dashboard/expression/get-expression-analytics?startTime=1648183720488&endTime=1650865720488 400 (Bad Request)
with error log
These calls are working fine, they have substantly less data though.
const { data: overall, error: psychometricError } = useSWRImmutable(
`dashboard/psychometric/get-psychometric-home?starttime=infinite`,
apiRequest
);
const { data: sentimentData, error: sentimentError } = useSWRImmutable(
[`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
fetchSentiment
);
Made an update to the fetch call to be more readable and specifically about the URL pathway.
import firebase from './firebase';
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
export async function expressionsRequest(path, method = 'GET') {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
return fetch(`/api/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
if (!response.ok) {
throw `Server error: [${response.status}] [${response.statusText}] [${response.url}]`;
}
return response.json();
})
.then((receivedJson) => {
if (receivedJson.status === 'error') {
// Automatically signout user if accessToken is no longer valid
if (receivedJson.code === 'auth/invalid-user-token') {
firebase.auth().signOut();
}
throw new CustomError(receivedJson.code, receivedJson.message);
} else {
return receivedJson.data;
}
})
.catch((err) => {
console.debug('Error in fetch', err);
throw err;
});
}
Additionally, this is what the lambda function (using next API folder) looks like,
const requireAuth = require('../../_require-auth');
const { db } = require('../../_sql');
export default requireAuth(async (req, res) => {
const { uid: id } = req.user;
const startTime = Math.round(req.query.startTime * 0.001);
const endTime = Math.round(req.query.endTime * 0.001);
const parameters = [id, startTime, endTime];
//sql injection definitely possible here, need to work out better method of dealing with this.
const sqlText = `SELECT a,b,c,d,e,f,g,h,i FROM tablename WHERE a=$1 AND i BETWEEN $2 AND $3;`;
try {
const { rows } = await db.query(sqlText, parameters);
return res.status(200).json({
code: 0,
data: rows,
});
} catch (error) {
return res.status(200).json({
code: 0,
message: 'Error occurred in getting tablename',
error,
});
}
});
using postman with the same query, i.e.,
curl --location --request GET 'http://localhost:3000/api/dashboard/expression/get-expression-analytics?startTime=1648387240382&endTime=1651069240382' \
--header 'Authorization: Bearer xxxx' \
--data-raw ''
Successfully returns a response with data attached.
Based on your first code blocks, the startDate value is getting passed into the fetcher as method, and the endDate value is getting passed into the fetcher as data. This is based on the useSWR docs about passing in an array for the key argument: https://swr.vercel.app/docs/arguments#multiple-arguments
If the code you provided is correct, I'd assume the 400 is coming from trying to pass in a random value for the method option for fetch.
This should be fixed by only passing the API endpoint path into useSWR instead of an array:
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);

How to set Authorization token with axios in node.js

I want to use axios in api test. To initialize client I need first to set auth token, which I expect to get with axios as well.How to get it form async code?
const a = require('axios');
getToken = () => {
var axios =a.create({
baseURL: 'http://local.v2.app.com/api/auth/v1.0',
headers: {'Content-Type': 'application/json'},
});
return axios.post('/credentials/login', {
username: '1#test.com',
password: 'Test#1234'
})
.then(function (response) {
return response.data.data.token;
})
.catch(function (error) {
console.log(error);
});
}
const client=a.create({
baseURL: 'http://local.v2.app.com/api/auth/v1.0',
headers: {
'Content-Type': 'application/json',
'Authorization': getToken()
},
});
module.exports = client;
First, getToken should be async (or just return promise):
async getToken() {
var axios = a.create({
baseURL: 'http://local.v2.app.com/api/auth/v1.0',
headers: {'Content-Type': 'application/json'},
});
try {
const response = await axios.post('/credentials/login', {
username: '1#test.com',
password: 'Test#1234'
})
return response.data.data.token;
} catch (error) {
console.error(error);
}
}
Then you can mock request:
const client = a.create({
baseURL: 'http://local.v2.app.com/api/auth/v1.0',
headers: {
'Content-Type': 'application/json'
}
})
const request = client.request
const addToken = (token) => (data, headers) => {
headers.Authorization = token
return data
}
client.request = (config = {}) => new Promise(async (resolve, reject) => {
token = await getToken()
if (!config.transformRequest) config.transformRequest = []
config.transformRequest.push(addToken(token))
request(config).then(resolve, reject)
})
module.exports = client
Yes, bit messy, feel free to refactor!
axios has request function which makes requests, others like get, post are aliases. This code patches request to get token first and then continue request.
transformRequest is took from axious readme on github, there is a comment "You may modify the headers object" so it is legal.
P.S good question, don't know why -1

Save a response Request.post token in variable

I just need to take the token returned in API to complete a URL. I need to take the response.body and save in a variable to use after.
I'm using the protractor to automation tests, and to open a URL I'm consuming an API that return a token, to be used as a parameter in a URL
describe('TEste API', function(){
var Request = require('request');
it("api Testing in protractor", async function (callback) {
let tokenReturn = "empty";
tokenReturn = Request.post({
"headers": { "content-type": "application/x-www-form-urlencoded" },
"url": "https://corpqa.sts.ford.com/adfs/oauth2/token",
"form": {
"grant_type":'client_credentials',
"client_id":'ad9cdf61-e863-4606-a90a-cf7b7141234',
"client_secret":'QmjeT5UZ0N1M0jOEcggrxgFzw-vrZY2UphAy21d5',
"resource":'urn:age:pcf:sss:dev',
}
}, (error, response, body) => {
if (error) {
return console.dir(error);
}
resp = JSON.parse(response.body);
console.log("inside: " + resp.access_token);
callback();
});
console.log("outside: www.example.com/?token=" + tokenReturn);
});
});//------------------------- end describe
In console show me.
outside:
www.example.com/?token=[object Object]
inside:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IldzZks3Q2FtMDZKY3dkR1Z6a2NiYVUzd21wZyJ9.eyJhdWQiOiJ1cm46YWdlOnBjZjpzdGY6ZGV2IiwiaXNzIjoiaHR0cHM6Ly9jb3JwcWEuc3RzLmZvcmQuY29tL2FkZnMvc2VydmljZXMvdHJ1c3QiLCJpYXQiOjE1NzMyMzIzNjYsImV4cCI6MTU3MzIzNTk2NiwiYXBwdHlwZSI6IkNvbmZpZGVudGlhbCIsImFwcGlkIjoiYWQ5Y2RmNjEtZTg2My00NjA2LWE5MGEtY2Y3YjcxNDE4OTQ1IiwiYXV0aG1ldGhvZCI6Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9hdXRoZW50aWNhdGlvbm1ldGhvZC9wYXNzd29yZCIsImF1dGhfdGltZSI6IjIwMTktMTEtMDhUMTc6MDQ6MjYuMTU5WiIsInZlciI6IjEuMCJ9.kuVfmgvN7_t4h2LB5o6dzTV2hngdapMrWFRPANISg5ayUnqeBMKHI5PWvISddfZ2qjO7kSPXlYVffhjrBhqAxY75EAhLX8hAmHDm_2jl49prtnsnqV-l-zhFaqCyfhEcgtVCRE_GX6EON2pewsX09Vdbn_2uHvh5wcGdWCnontzkZdrf__X8-tuE5R7tHrtge0ZXMdx5bCF7INKzA1YolTwxOOiYNVvZFPDKLRwa4VUf_qTKN5BmLisRVN4gmnTzGTPXjXlHZApRwJAbXR4V7VhtVQ6VcjHuyYIpp_rK0K7kQjwu0FLpE1FHZTNRwvXNI1VqyhGaanx2bM_59NyDgg
I just want the outside result to be equal to the inside with token.
I made some changes but the result change to UNDEFINED instead OBJECT.
Maybe use promises ?
Example:
function requestToServer(){
return new Promise((resovle, reject) => {
Request.post({
"headers": { "content-type": "application/x-www-form-urlencoded" },
"url": "https://corpqa.sts.ford.com/adfs/oauth2/token",
"form": {
"grant_type":'client_credentials',
"client_id":'ad9cdf61-e863-4606-a90a-cf7b7141234',
"client_secret":'QmjeT5UZ0N1M0jOEcggrxgFzw-vrZY2UphAy21d5',
"resource":'urn:age:pcf:sss:dev',
}
}, (error, response, body) => {
if (error) {
return console.dir(error);
}
resp = JSON.parse(response.body);
console.log("inside: " + resp.access_token);
resovle(resp.access_token);
callback();
});
});
}
describe('TEste API', function(){
var Request = require('request');
it("api Testing in protractor", async function (callback) {
let tokenReturn = await requestToServer();
console.log("outside: www.example.com/?token=" + tokenReturn);
});
});//------------------------- end describe

request(...).then is not a function error when making a POST request

I'm trying to create a firebase function that makes a HTTP POST request whenever a new document is created.
This is my code:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
const request = require("request");
exports.sendMessage = functions.firestore.document('comms/{comms}').onCreate((snap, context) => {
const newValue = snap.data();
if (newValue) {
//const email = newValue.email;
const msg = newValue.msg;
return request({
uri: "url",
method: 'POST',
body: msg,
json: true,
resolveWithFullResponse: true
}).then((response: { statusCode: number; }) => {
if (response.statusCode >= 400) {
throw new Error(`HTTP Error: ${response.statusCode}`);
}
console.log('SUCCESS! Posted', msg);
});
}
return Promise
});
Error received:
TypeError: request(...).then is not a function
at exports.sendMessage.functions.firestore.document.onCreate (/srv/lib/index.js:25:12)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:127:23)
at /worker/worker.js:825:24
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
request supports callback interfaces natively but does not return a promise, which is what you must do within a Cloud Function.
This is explained in the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
You could use request-promise (https://github.com/request/request-promise) and the rp() method which "returns a regular Promises/A+ compliant promise". You would then adapt your code as follows:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
const rp = require('request-promise');
exports.sendMessage = functions.firestore.document('comms/{comms}').onCreate((snap, context) => {
const newValue = snap.data();
if (newValue) {
const msg = newValue.msg;
var options = {
method: 'POST',
uri: '....',
body: msg,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
.then(parsedBody => {
// POST succeeded...
console.log('SUCCESS! Posted', msg);
return null;
})
.catch(err => {
// POST failed...
console.log(err);
return null;
});
} else {
return null;
}
});
request module doesn't return a Promise instead try using a callback function for response.
return request({
uri: "url",
method: 'POST',
body: msg,
json: true,
resolveWithFullResponse: true
}, function (error, response, body) {
})
As in the documentation already mention you need to pass the callback to your request
var request = require('request');
request('http://www.google.com', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
If you want to chain your request you can use pipe
request
.get('url/img.png')
.on('response', function(response) {
console.log(response.statusCode) // 200
console.log(response.headers['content-type']) // 'image/png'
})
.pipe(request.put('url'))
If you want to use promise you can use request-promise
var rp = require('request-promise');
rp('http://www.google.com')
.then(function (htmlString) {
// Process html...
})
.catch(function (err) {
// Crawling failed...
});
The request module work on callbacks only, If you want to make Promisify you need to do like this
const request = require('request');
const webService = {};
webService.callApi = (url, bodyObj, method) => {
return new Promise((resolve, reject) => {
const options = {
method: method || 'POST',
url: url,
headers: {
'Content-Type': 'application/json',
},
body: bodyObj,
json: true,
};
// Error Handler
const errorMessge = { code: 500, error: 'INTERNAL_SERVER_ERROR' };
request(options, (error, response, resBody) => {
if (error) {
return reject(errorMessge);
} else if (response.statusCode !== 200) {
return reject(errorMessge);
}
return resolve(resBody);
});
});
};
module.exports = webService;

Using request-promise to make an API call using jwt. [ERR_INVALID_ARG_TYPE] received

I'm learning nodejs and trying to make an API call. The API uses JWT to authenticate.
I created these functions to sign a token:
function token() {
const payload = {
iat: Math.floor(new Date() / 1000),
exp: Math.floor(new Date() / 1000) + 30,
sub: "api_key_jwt",
iss: "external",
jti: crypto.randomBytes(6).toString("hex")
};
return new Promise((resolve, reject) => {
jwt.sign(payload, privatekey, { algorithm: "RS256" }, function(
err,
token2
) {
if (err) reject(err);
else resolve(token2);
});
});
}
exports.genToken = async function() {
const header = {
"x-api-key": api
};
const data = {
kid: api,
jwt_token: await token()
};
async function authorization(req, res) {
try {
const auth = await rp({
url: authurl,
method: "POST",
headers: header,
body: data
});
res.send(auth.body);
} catch (error) {
res.send(404).send();
}
}
return {
"x-api-key": api,
Authorization: "Bearer " + authorization()
};
};
This works fine. Then I created a function to make the API call:
const token = require("./index").genToken;
const rp = require("request-promise");
exports.getOrderBook = function(res, error) {
const full_url = url + "order_book";
const auth = token();
rp({
url: full_url,
method: "GET",
headers: auth,
body: {
market: "btceur"
},
json: true
})
.then(function(response) {
res(response);
})
.catch(function(err) {
error(err);
});
};
And I call it using Express:
routes.get("/orderbook", async (req, res, next) => {
try {
const book = await orders.getOrderBook();
res.send(book);
} catch (error) {
next(error);
}
});
However, when I call my API, it shows an error in console:
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of
type string or Buffer. Received type object.
I guess the error is something with the token generation, because if I console.log(auth) in the getOrderBook function, it shows Promise { <pending> }, so probably an object is being passed as the jwt token.
Is it really the problem? I tried a lot of different solutions that I found on internet, however the concept of Async/Await is new to me, and I'm having some troubles to figure it out.
Thanks a lot in advance guys!
Since getToken is an anync function, the return is wrapped in a Promise as well so you would need another anync/await:
exports.getOrderBook = async function() {
let response;
try {
const full_url = url + "order_book";
const auth = await token();
response = await rp({
url: full_url,
method: "GET",
headers: auth,
body: {
market: "btceur"
},
json: true
});
} catch (e) {
// handle error
throw e
// or console.error(e)
}
return response;
};
In this line as well Authorization: "Bearer " + authorization(), authorization is returning a promise
const bearer = await authorization()
return {
"x-api-key": api,
Authorization: "Bearer " + bearer
};
For error handling wrap entire thing in try..catch block
exports.genToken = async function() {
try {
const header = {
"x-api-key": api
};
const data = {
kid: api,
jwt_token: await token()
};
async function authorization(req, res) {
let auth;
try {
auth = await rp({
url: authurl,
method: "POST",
headers: header,
body: data
});
// res object not available
// res.send(auth.body);
} catch (error) {
// res object not available, better throw error and handle in your middleware
// res.send(404).send();
}
return auth
}
const bearer = await authorization()
} catch (e) {
// handle error
}
return {
"x-api-key": api,
Authorization: "Bearer " + bearer
};
}

Categories