Google Cloud Functions finishes with: timeout when using Axios - javascript

I want to use a Google Cloud Function with a http trigger to write data into a Google Spreadsheet.
The following code is my cloud function:
exports.writeDataMaterialCollection = functions.https.onRequest(
(req, res) => {
if (req.method === "POST") {
console.log(req.body);
res.set("Access-Control-Allow-Origin", "*");
const sheets = google.sheets({ version: "v4" });
var jwt = getJwt();
var apiKey = getApiKey();
var spreadsheetId = "sheetIDxxxxxxxxx";
var range = "A:L";
var row = ["data"];
sheets.spreadsheets.values.append(
{
spreadsheetId: spreadsheetId,
range: range,
auth: jwt,
key: apiKey,
valueInputOption: "RAW",
resource: { values: [row] }
},
(err, result) => {
if (err) {
throw err;
} else {
console.log(result.data.updates.updatedRange);
res.status(200).send(result.data.updates.updatedRange);
}
}
);
}
}
);
When I make a curl POST request, then the data is written to the spreadsheet correctly.
url -d '{"test": "wert"}' -X POST http://localhost:5001/XXXX/writeDataMaterialCollection
Problem
What I don't understand is when I use Axios inside Vue.js, Google Cloud Function returns throw new Error("Function timed out.")
axios(
"http://localhost:5001/XXXXX/writeDataMaterialCollection",
{
method: "POST",
headers: {
"content-type": "application/json",
"Access-Control-Allow-Origin": "*"
},
data: {
daten1: 23
}
}
).then(response => (self.writeDataResult = response));

If your function gets to the point of this line:
throw err;
It will not actually terminate the function and propagate that error to the client. That's because you're throwing an error out of a callback function, not the main function. This also means that function will timeout, because no response is being sent to the client.
What you should do instead is send an error to the client, so the function can correctly terminate, and the client can receive the error. You might want to consider logging the error as well, so you can see in the console what went wrong:
if (err) {
res.send(500);
console.error(err);
}

Related

POST method to express server returning 404 [duplicate]

This question already has an answer here:
Express routes parameters
(1 answer)
Closed last month.
I'm trying to send a POST to my server, in order to edit a user's details. I've made sure it's sending to the right URL, however get a 404 response. GET requests work fine, however my POST doesn't seem to get through for whatever reason. I've been searching for solutions for a while, with no luck hence posting here!
user.js (server side)
userRoutes.route('/user/update/:id').post(function (req, response) {
let db_connect = dbo.getDb("preview");
let myquery = { _id: ObjectId(req.params.id) };
let newValues = {
$set: {
name: req.body.name,
user_name: req.body.user_name
},
};
db_connect
.collection("users")
.updateOne(myquery, newValues, function (err, res) {
if (err) throw err;
console.log('user updated');
response.json(res);
})
});
middleware
export const updateUser = async (id, userDetails) => {
const endpoint = `${serverIp}/user/update/?id=${id}`;
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(userDetails)
})
const result = await response.json();
return result;
} catch (error) {
console.log(error)
}
}
and a simple function to handle submitting
function handleSave() {
const newUserDetails = {
name: accountHolder,
user_name: accountUsername
};
updateUser(userId, newUserDetails);
}
Under networking in dev tools I can see the URL is indeed correct, so I can't see why this isn't working
chrome dev tools
Any help would be greatly appreciate it!
I've tried sending a basic response (i.e. a string instead of object), changing the endpoint, and more all to no avail
It seems like you are passing the id as a query param and not as part of the path
const endpoint = `${serverIp}/user/update/?id=${id}`;
^
What I can see from first glance is that in server-side you are using request parameter for id, but in the client you're sending id as a request query

MailChimp Error Status: 401 Title: "API Key Invalid"

I am following a MailChimp API tutorial
When I test the API, I get a 401 response saying my API key is invalid.
Error -
Status: 401
"Your API key may be invalid, or you've attempted to access the wrong datacenter."
I have yet to register a domain yet, this is being testing using a local server. Could this be error be caused by MailChimp's refusing the request for another reason, perhaps CORS?
app.post('/signup', (req, res) => {
// Get form data
const { email } = req.body;
// Make sure field is filled
if(!email) {
res.redirect('/html/fail.html');
return;
}
// Construct req data
const data = {
members: [
{
email_address: email,
status: 'subscribed'
}
]
}
// Convert to JSON
const postData = JSON.stringify(data);
const options = {
url: 'https://us19.api.mailchimp.com/3.0/lists/listID',
method: 'POST',
headers: {
Authorization: 'auth xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-us19'
},
body: postData
};
request(options, (err, response, body) => {
if(err) {
console.log(err);
res.redirect('/html/fail.html');
} else {
if(response.statusCode === 200) {
res.redirect('/html/success.html');
} else {
console.log(response.body);
res.redirect('/html/fail.html');
}
}
});
})
I tried running the same code in request in PostMan and I got back a 200 response.
I was initially importing the API key from a config file, that I had not destructured...

How to debug a POST request to a Node.js Express application?

I'm new to nodejs and I'm migrating my current API from python to nodejs using express.
What I'm trying to do is to make a request to an external API. I'm pretty sure my API call is right, since I copied from the external API example:
exports.getBalance = function() {
return new Promise(function(resolve, reject) {
var command_url = "/v1/transaction/getBalance";
var full_url = API_URL + command_url;
var nonce = getNonce();
var data = "username=" + convertUsername(API_USER) + "&nonce=" + nonce;
const signature = makeSignature(nonce, data, command_url);
var form = {
username: API_USER,
nonce: nonce
};
var formData = querystring.stringify(form);
var contentLength = formData.length;
var headers = {
"X-API-KEY": API_KEY,
"X-API-SIGN": signature,
"X-API-NONCE": nonce,
"Content-Length": contentLength,
"Content-Type": "application/x-www-form-urlencoded"
};
request(
{
url: full_url,
method: "POST",
headers: headers,
body: formData
},
function(error, response, body) {
if (!error) {
body = JSON.parse(body);
if (response.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
} else {
console.log("error:", error);
reject(error);
}
}
);
});
This is my express route:
routes.post("/balance", mistertango.getBalance);
However, when I try to POST to this route, I don't receive nothing. I use Insomnia to run API tests, so Insomnia keeps running with no response from my express API.
I'd like to know how can I debug my code? I'd like to make an API call using Insomnia to my express API, and check if I'm getting a response from my external API request.
Thanks
mistertango.getBlance returns a Promise but express doesn't handle promises by default. You need to call res.send(data) to actually send a response to the client.
routes.post("/balance", async (req, res, next) => {
try {
const balance = await mistertango.getBalance()
res.send({ balance })
} catch (error) {
next(error)
}
})
Or without async/await:
routes.post("/balance", (req, res, next) => {
mistertango.getBalance()
.then(balance => res.send({ balance }))
.catch(error => next(error))
})
Note 1: You might be able to use res.send(balance) instead of res.send({ balance }) as long as balance is not a number. (Response body cannot be a raw number, so I've wrapped it in an object).
Note 2: In both cases, we have to use .catch or try/catch to handle any errors because express won't handle rejected promises on its own. You can use express-promise-router to fix that!

How should I pass variables from my external routes to a javascript controller in Node.js asynchronously?

My external routes is setup so that a POST form sends data to my server and my server file validates the input. It then takes this input and passes it to another file which will then perform authentication. I have a few questions regarding this, which are:
1) Do I need to pass the data asynchronously?
2) Is the way I am currently passing synchronous? Should it be changed
3) If I want to pass my variables into this file and it uses an async waterfall, (I believe I can't pass variables into the first function) should I create a main function which has two sub functions (one for storing the variables and one for executing the async waterfall)?
I am new to Node.js so have only just started learning the concept of asynchronous functionality. Any tips, help or questions, please let me know. If i have left something out please ask
My Server function is as such:
//Route for POST requests to Login
//First Sanitise input!
app.route('/login').post([
//ACCOUNTTYPE CHECKS -------------------------------------------------------------------------------
check('accountType').exists()
.trim()
.isAlpha()
.isLength({
min: 8,
max: 10
})
.matches(/(Production|Internal)/).withMessage('Must be either \'Production\' or \'Internal\'\!')
.escape(),
//CUSTOMERID CHECKS --------------------------------------------------------------------------------
check('customerID').exists()
.trim()
.isNumeric()
.isInt()
.isLength({
max: 40
})
.escape(),
//CLIENTID CHECKS ---------------------
check('clientID').exists()
.trim()
.isAlphanumeric()
.isLength({
max: 40
})
.escape(),
//CLIENTSECRET CHECKS ---------------------
check('clientSecret').exists()
.trim()
.isAlphanumeric()
.isLength({
min: 0,
max: 45
})
.escape(),
//USERNAME CHECKS ---------------------
check('username').exists()
.trim()
.isEmail()
.isLength({
min: 0,
max: 100
})
.escape(),
//PASSWORD CHECKS ---------------------
check('password').exists()
.trim()
.isLength({
min: 0,
max: 100
})
.escape()
], urlencodedParser, (req, res) => {
let now = new Date().toString(),
log = `${now}: ${req.method} ${req.url}`,
sess = req.session;
console.log(req.body);
fs.appendFile('server-requests.log', log + '\n', (err) => {
if (err) {
console.log('Unable to append to server-requests.log!');
}
});
// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
if (!errors.isEmpty()) {
//return a bad status and array of errors
//if the validation fails
// return res.status(422).json({
// errors: errors.array()
// });
//OR:
return res.render('login.hbs', {
currentYear: new Date().getFullYear(),
//This does not work realtime - FIX TODO
currentTime: moment().format('MMMM Do YYYY - h:mm:ss a'),
data: {},
errors: errors.mapped()
});
// If there aren't any errors, proceed to save variable data and use 2.0
} else {
//Handle with event emiitters and make asynchronous - TODO
const data = matchedData(req);
console.log('Data stored in session:')
console.log('Sanitised:', data);
//Store Form Data in session variables
sess.accountType = data.accountType,
sess.customerID = data.customerID,
sess.clientID = data.clientID,
sess.clientSecret = data.clientSecret,
sess.username = data.username,
sess.password = data.password;
console.log('Sanitised POST Form! Now initialising request.');
req.flash('success', 'Successful POST request, proceeding to authorisation...)');
//Make asynchronous?
//use each function and get variables
auth.saveVariables(sess.accountType, sess.customerID, sess.clientID, sess.clientSecret, sess.username, sess.password);
auth.asyncWaterfall(callback)
res.redirect('/index');
//Or alternatively should it redirect with a query string?
// const url = require('url');
// app.get('/index', function (req, res) {
// res.redirect(url.format({
// pathname: "/",
// query: {
// "acccountType": 1,
// "customerID": 2,
// "clientID": 3,
// "clientSecret": 4,
// "username": 5,
// "password": 6
// }
// }));
// });
}
})
Here is the start of my auth controller code:
const saveVariables = (accountType, customerID, clientID, clientSecret, username, password, callback) => {
//Test parameters
if (accountType.type() !== String || customerID.type() !== String || clientID.type() !== String || clientSecret.type() !== String || username.type() !== String || password.type() !== String) {
let fields = [accountType, customerID, clientID, clientSecret, username, password];
test(fields);
}
accountType,
customerID,
clientID,
clientSecret,
username,
password;
}
const test = (array) => {
array.forEach((field) => {
let errorString;
if (field.type() !== String) {
errorString += `The field ${field} is not a String. Type: ${field}.`
}
return errorString;
})
}
//This const creates the auth URL and auth code grant URL. It also assigns the host URL.
const createAuthURL = (callback) => {
//If the paramter test passes then proceed
if (accountType === "Production") {
try {
let authURL = `https://${prodURL}${url1}`,
authCodeURL = `https://${prodURL}${url2}`,
host = prodURL;
} catch (e) {
console.log(`Error occurred when assigning url variables (Production). Error: ${e}`);
console.error('Error occurred when assigning url variables (Production). Error:', e);
}
} else if (accountType === "Internal") {
try {
let authURL = `https://${internURL}${url1}`,
authCodeURL = `https://${internURL}${url2}`,
host = internURL;
} catch (e) {
console.log(`Error occurred when assigning url variables (Internal). Error: ${e}`);
console.error('Error occurred when assigning url variables (Internal). Error:', e);
}
} else {
console.log(`Bad String Input into auth URL! Entry: ${accountType}`);
console.error('Bad String Input into auth URL: ', accountType);
}
}
const asyncWaterfall = (req, res) => {
//Asynchronous Waterfall Call of 'Getting Access Tokens' (Stages 1-3)
async.waterfall([
getCookieData,
getAuthCode,
getAccessToken
], (err, result) => {
if (err) {
alert('Something is wrong!');
}
return alert('Done!');
});
//STAGE 1 - USE CUSTOMER DETAILS TO RETRIEVE COOKIE AND CSRF SESSION DATA
getCookieData = async (callback) => {
//For testing purposes - Remove after development
console.log(`The type of account used is: ${accountType}`);
console.log(`The customerID used is: ${customerID}`);
console.log(`The clientID used is: ${clientID}`);
console.log(`The client secret used is: ${clientSecret}`);
console.log(`The username used is: ${username}`);
console.log(`The password used is: ${password}`);
console.log(`Making a URL request to: ${authURL}`);
//If the paramter test passes then proceed
var options = {
//Type of HTTP request
method: 'POST',
//URI String
uri: authURL,
//HTTP Query Strings
qs: {
'client_id': clientID
},
//HTTP Headers
headers: {
'cache-control': 'no-cache',
'Accept': 'application/json',
'Host': host,
'Content-Type': 'application/json'
},
//HTTP Body
body: {
'username': username,
'password': password
},
json: true // Automatically stringifies the body to JSON
//resolveWithFullResponse: true // Get the full response instead of just the body
//simple: false // Get a rejection only if the request failed for technical reasons
};
console.log(`Beginning HTTP POST Request to ${authURL}...`);
//await literally makes JavaScript wait until the promise settles, and then go on with the result.
await rp(options)
.then((parsedBody) => {
//POST Succeeded...
Console.log('Successful HTTP POST!');
try {
let csrf = response.body('csrftoken'),
ses = response.body('session'),
sesk = `session=${ses}`;
} catch (e) {
console.log(`STAGE 1 - Error occurred when assigning url variables. Error: ${e}`);
console.error('STAGE 1 - Error occurred when assigning url variables. Error:', e);
}
console.log(`Successful grab of the cookie: ${ses} and csrf token: ${csrf}. Getting authorisation code now!`);
//Asynchronous callback for the next function - return = defensive architecture
return callback(null, authCodeURL, customerID, clientID, csrf, sesk);
})
.catch((err) => {
if (res.statusCode == 400) {
console.log(`Error Message: ${res.body.message}. Status: ${res.body.status}`);
console.error('Error Message:', res.body.message, 'Status:', res.body.status);
} else if (res.statusCode == 401) {
console.log(`Error Message: ${res.body.message}. Status: ${res.body.status}`);
console.error('Error Message:', res.body.message, 'Status:', res.body.status);
} else {
console.log(`Failed to retrieve the cookie data! Error: ${error}`);
console.error('Failed to retrieve the cookie data! Error:', error);
}
});
},
//STAGE 2 - USE COOKIES AND CSRF TOKEN TO GET AUTH CODE
//Is the word async needed? it is not asyncchronous but sequential
getAuthCode = async (authCodeURL, customerID, clientID, csrf, sesk, callback) => {
//Make sure all the data is in string format - Run Time Tests
if (authCodeURL !== String || cutomerID !== String || clientID !== String || csrf !== String || sesk !== String) {
let fields = [authCodeURL, cutomerID, clientID, csrf, sesk];
test(fields);
}
//If successful, proceed:
var options = {
method: 'POST',
uri: authCodeURL,
qs: {
'client_id': clientID,
'response_type': 'code',
'scope': 'all'
},
headers: {
'X-CSRF-TOKEN': csrf,
'Accept': 'application/json',
'Cookie': sesk,
'Content-Type': 'application/json'
},
body: {
'customer_id': customerID
},
json: true // Automatically stringifies the body to JSON
};
console.log(`Beginning HTTP POST Request to ${authCodeURL}...`);
//await literally makes JavaScript wait until the promise settles, and then go on with the result.
await rp(options)
.then((parsedBody) => {
//POST Succeeded...
Console.log('Successful HTTP POST!');
try {
let authCode = response.body.JSON('auth_code'),
swapurl = `https://${host}${url3}`;
} catch (e) {
console.log(`STAGE 2 - Error occurred when assigning url variables. Error: ${e}`);
console.error('STAGE 2 - Error occurred when assigning url variables. Error:', e);
}
console.log(`The authorisation Code is ${authcode}. Getting Access Token now!`);
//Asynchronous callback for the next function - return = defensive architecture
return callback(null, swapURL, clientID, clientSecret, authCode);
})
.catch((err) => {
if (res.statusCode == 400) {
console.log(`Error Message: ${res.body.message}. Extra: ${res.body.extra}`);
console.error('Error Message:', res.body.message, 'Extra:', res.body.extra);
} else {
console.log(`Failed to retrieve the authorisation code! Error: ${error}`);
console.error('Failed to retrieve the authorisation code! Error: ', error);
}
});
},
//STAGE 3 - USE AUTH CODE TO GET ACCESS TOKEN
//ASYNC NEEDED?
getAccessToken = async (swapURL, clientID, clientSecret, authCode, callback) => {
//Make sure all the data is in string format - Run Time Tests
if (swapURL !== String || clientSecret !== String || clientID !== String || authCode !== String) {
let fields = [swapURL, clientSecret, clientID, authCode];
test(fields);
}
//If successful, proceed:
var options = {
method: 'POST',
uri: swapURL,
qs: {
'client_id': clientID,
'grant_type': 'authorization_code',
'client_secret': clientSecret,
'code': authCode
},
json: true // Automatically stringifies the body to JSON
};
console.log(`Beginning HTTP POST Request to ${swapURL}...`);
//await literally makes JavaScript wait until the promise settles, and then go on with the result.
await rp(options)
.then((parsedBody) => {
//POST Succeeded...
Console.log('Successful HTTP POST!');
try {
let accessToken = response.body('access_token'),
refreshToken = response.body('refresh_token');
} catch (e) {
console.log(`STAGE 3 - Error occurred when assigning url variables. Error: ${e}`);
console.error('STAGE 3 - Error occurred when assigning url variables. Error:', e);
}
console.log(`The access Token is ${accessToken} and the refreshToken which is ${refreshToken}! These are only valid for 2 hours!`);
//Asynchronous callback for the waterfall - return = defensive architecture
return callback(null, 'done');
})
.catch((err) => {
console.log(`Failed to retrieve the access/refresh Token! Error: ${error}`);
console.error('Failed to retrieve the access/refresh Token! Error:', error);
});
}
}

Get Current Users: Google Real Time API with NodeJS & gtoken: "Invalid Credentials"

I would like to use a NodeJS Server to obtain the current users on my Website from Google Analytic using the Real Time Reporting API:
So far I try to do this via an HTTP request with request and gtoken. The getToken-Part works. I get a token. But the HTTP-Request doesnt work. I get an "Invalid Credentials" Error with Code 401.
Does anybody have an idea what to do? Maybe this is the completely wrong approach to get these data.
var received_token;
var url = "https://www.googleapis.com/analytics/v3/data/realtime";
var paramsObject = { ids:"ga:123456789"};
const gtoken = new GoogleToken({
keyFile: 'pathToServiceAccountJSONKeyFile:',
scope: ['https://www.googleapis.com/auth/analytics.readonly']
});
gtoken.getToken(function(err, token) {
if (err) {
console.log(err);
return;
}
received_token = token;
console.log(token);
request({
url: url,
qs: paramsObject,
headers: {
'Authorization': received_token
}
}, function(err, response, body) {
if(err) { console.log(err); return; }
// console.log(response);
console.log(body);
});
I found the error :-)
In the Authorization Header "Bearer" was missing, now it works like charm and I receive data from the Google Real Time API.
headers: {
'Authorization': "Bearer " +received_token
}

Categories