firebase http function termination and promise - node.js

I have the following firebase cloud function
exports.sendToUser = functions.region('europe-west1').https.onRequest((request, response) => {
console.log('start');
const receiverUid = request.body.receiverId;
const requestingUid = getUserIdFromRequest(request);
// Notification details.
const payload = {
"data": {
"type": request.body.type,
"mainData": request.body.mainData
}
};
const options = {
"priority": "high"
}
console.log('check');
requestingUid.then(uid => {
console.log('complete');
payload.data.senderId = uid;
sendMessageToUser(receiverUid, payload, options);
response.status(200).send();
}).catch(err => {
console.error(err);
response.status(401).send(err);
});
});
My problem with this function is that it prints "start" and "check" all fine but doesn't print the "complete" or the "receiver" nor does it execute the "sendMessageToUser" function. However, the status 200 is responded and the function terminates which I can see in the logs in the developer console and on the client device.
Also, when replacing line response.status(200).send(); with response.status(400).send(); or some other response code the correct status (400) is sent but the logs are not printed and the "sendMessageToUser" function not executed.
From time to time, however, the function is properly executed so I'm assuming that the function is somehow prematurely terminated. But how can the correct response code be sent then? I'm aware of the necessity to return a promise from asynchronus functions but as this is a http-triggered (synchronus) function I wouldn't know how to handle this.
Besides, yesterday was the first time this particular error occured. Before that, it worked fine. So can anybody explain what is happening here?

Related

stopping postman after request is done

I am trying to create a Weather API using node. In my controller file, I have this code which is run for the /check route.
controller.js:
//Check Weather
exports.check = (req, res) => {
UserModel.check(req.body.city)
};
model.js:
//Check Weather
function getData(city) {
url = "something";
request(url, function (err, response, body) {
if(err){
console.log('error:', error);
} else {
console.log('body:', body);
}
});
}
exports.check = (city) => {
city = city.toLowerCase();
let values = getData(city);
console.log(city);
return(values);
};
route:
app.post('/check', [
UsersController.check
]);
When I run this, it functions properly and the correct thing is logged in the console. However, after I send a request in Postman and the console.log shows up, Postman seems to be hung up as seen in this pic. Is there anyway I can make it so that Postman stops sending the request after return or console.log?
Postman is waiting for a response from the server. Your code is not currently sending any response, so postman seems 'hung up' as it is waiting. Try changing the line saying UserModel.check(req.body.city) to say res.send(UserModel.check(req.body.city)) so it will send the data returned from your UserModel.check function back as the response. Alternatively, if you don't want to send back the returned value, you could just add res.send(PutWhateverYouWantSentHere) after the function call.

Proper way to report error back in Firebase Functions HTTPS request in nodejs

I implemented a Firebase function to be called plainly on HTTPS via browser (I use postman for testing) in node.js :
exports.notifToAdmin = functions.https.onRequest((request, response) => {
const title = request.query.title
const body = request.query.body
const badge = request.query.badge
if (typeof title === 'undefined') { return response.status(500).send("title missing") }
if (typeof body === 'undefined') { return response.status(500).send("body missing") }
if (typeof badge === 'undefined') { return response.status(500).send("badge missing") }
notifications.sendNotifToAdmin(title, body, badge)
.then(message => {
const ackString = fingerPrint(msg);
return response.send(ackString);
})
.catch(error => {
console.error(error);
return response.status(500).send(error);
});
});
am I using a correct way to send errors back to the caller (via the response.status(500).send("...."))? In the Firebase errors documentation I see the usage of throw new Error(...). So I am unsure if what I do is the most optimal way? I did notice the doc saying //Will cause a cold start if not caught(linked to this throw error), I don't want to restart anything just report an error to the caller...
I know that the onRequest result should be a promise should I change my code and put a return in front of the notifications.SendNotifToAdmin(...) (this returns a promise) but how does this add up with the return response.send(...)? Is this also returning a promise then?
am I using a correct way to send errors back to the caller (via the response.status(500).send("...."))
Yes, that is standard for HTTP type functions that need to send an HTTP status code. But you should send a 4xx range HTTP codes for errors that are related to the client sending incorrect information.
I know that the onRequest result should be a promise
There is absolutely no obligation for an onRequest type function to return a promise. The function just needs to send a response after all promises are resolved so that the async work can complete before the function is terminated when the response is delivered.

Twilio programmable SMS not sending in deployed Lambda function

I am working on a Serverless AWS service that uses Twilio's programmable SMS to deliver text messages.
My setup is consistently delivering messages successfully when I run the stack locally (e.g. sls offline start), but in the deployed environment I seem to be unable to even call the method on the Twilio client.
Here's how the message delivery is set up:
const twilio = require('twilio');
const twilioClient = twilio(
process.env.TWILIO_SID,
process.env.TWILIO_TOKEN,
{
lazyLoading: true,
}
);
export function sendMessage(user, message) {
twilioClient.messages.create({
from: process.env.TWILIO_NUMBER,
to: user.phone,
body: message,
}, function(err, message) {
console.log('error', err);
console.log('message', message);
});
}
// And then usage in a Serverless Function Handler
function example(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
// user is also determined here
sendMessage(user, 'This is a message');
return {
body: JSON.stringify({}),
statusCode: 200
};
}
Locally, running this works and I am able to see the output of the message log, with nothing on the error log. However, when deployed, running this yields nothing -- the method appears to not even get called (and I can verify in the Twilio logs that no API call was made), therefor no error or message logs are made in the callback.
In debugging I've tried the following:
I've logged all the environment variables (Twilio SSID, auth token, phone number), as well as the function arguments, and they all appear to be in place. I also checked out the Lambda function itself to make sure the environment variables exist.
I've examined my CloudWatch logs; no errors or exceptions are logged. Other than the Twilio method not getting called the Lambda function is executed without issue.
I've tried logging things like twilio and twilioClient.messages.create to make sure the client and function definition didn't get wiped out somehow.
I thought maybe it had to do with context.callbackWaitsForEmptyEventLoop so I changing it from false to true.
I have come up empty, I can't figure out why this would be working locally, but not when deployed.
Edit: per the Twilio client example, if you omit the callback function the method will return a Promise. I went ahead and tried to await the response of the method:
export function sendMessage(user, message) {
return twilioClient.messages.create({
from: process.env.TWILIO_NUMBER!,
to: user.phone,
body: message,
});
}
// Usage...
async function example(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
try {
const message = await sendMessage(user, 'This is a message');
console.log('message', message)
} catch (error) {
console.log('error', error);
}
return {
body: JSON.stringify({}),
statusCode: 200
};
}
In this example the Lambda function is successful, but neither the message nor the error are logged.
I tried this and it works. I tried to make my code as similar to use, with a few changes.
const twilio = require('twilio');
const twilioClient = twilio(
process.env.TWILIO_SID,
process.env.TWILIO_TOKEN
);
let user = '+14075551212';
function sendMessage(user, message) {
return twilioClient.messages.create({
from: process.env.TWILIO_NUMBER,
to: user,
body: message,
});
}
exports.handler = async function(event, context, callback) {
try {
const message = await sendMessage(user, 'This is a message');
console.log('message', message);
callback(null, {result: 'success'});
} catch (error) {
console.log('error', error);
callback("error");
}
};

Nodejs Telegram Bot: sendChatAction while waiting for network call

I am writing a Telegram bot using the Telegraf framework in Nodejs, and would like to display the 'bot is typing...' banner while the bot is performing a networking call.
The issue that I have is that my function isn't executed like how it is supposed to be, ie the 'typing' banner appears and disappears after 5 seconds (as per the docs), but the invoice never gets sent until at least 30 seconds later. Assessing the logs also shows that the console logs were executed after the function ends.
Here is my implementation:
module.exports = (bot) => bot.command('buy', (ctx) => {
let catalogueId = //some regex
return Promise.all([
ctx.replyWithChatAction('typing'),
sendInvoice(catalogueId, ctx)
])
})
function sendInvoice(catalogueId, ctx) {
console.log('1')
return helper.getItem(catalogueId)
.then((item) => {
console.log('2')
return createInvoice(item, ctx)
.then((invoice) => {
console.log('3')
return ctx.replyWithInvoice(invoice)
})
})
}
The log statements as seen in my Firebase Cloud Functions are like so:
As you can see, the console logs are printed after the function has ended, and are at almost 15 seconds apart. Other attempts:
//Attempt 2, same result as above
module.exports = (bot) => bot.command('buy', (ctx) => {
let catalogueId = //some regex
return ctx.replyWithChatAction('typing')
.then(() => sendInvoice(catalogueId, ctx))
})
//Attempt 3, log statements are printed before function ends, but the 'typing' only shows at the end. Need it to show when the networking calls starts
module.exports = (bot) => bot.command('buy', (ctx) => {
let catalogueId = //some regex
return sendInvoice(catalogueId, ctx))
})
function sendInvoice(catalogueId, ctx) {
console.log('1')
return helper.getItem(catalogueId)
.then((item) => {
console.log('2')
return createInvoice(item, ctx)
.then((invoice) => {
console.log('3')
return ctx.replyWithChatAction('typing')
.then(() => ctx.replyWithInvoice(invoice))
})
})
}
My bot is currently deployed on Firebase Cloud Function.
I had a similar problem with a bot running on AWS Lambda. My bot would send the typing chat action correctly to the chat, then all of a sudden the lambda function would quit and none of my other code would be processed.
I'm not an expert but I think that as soon as the handler function you provide to Firebase Cloud Function (or Lambda) returns something to the client that initiated your request, Firebase considers the request complete and shuts down your function invocation. The default webhook callback that Telegraf provides sends a response to the client when your bot replies with a chat action or a message. Consequently, if you export that default webhook reply as your handler (as I did) then your handler will return a response as soon as your bot replies with anything, including a chat action, and the rest of your code may not be processed.
I solved my problem by replacing this:
export const handler = makeHandler(bot.webhookCallback("/"))
with this:
export const handler = async (event, context, callback) => {
const tmp = JSON.parse(event.body) // get data passed to us
await bot.handleUpdate(tmp) // make Telegraf process that data
return callback(null, {
// return something for webhook, so it doesn't try to send same stuff again
statusCode: 200,
body: "",
})
}
(copied from this excellent github comment)
As you can see, instead of returning whenever the bot's webhook callback returns something, I'm now waiting until the update is completely handled and only returning afterward. Thus I can reply with a chat action, wait for my code to do some processing, reply with a message, wrap things up, and only then will my function invocation stop processing.

Failing test displays "Error: timeout of 2000ms exceeded" when using Sinon-Chai

I have the following route (express) for which I'm writing an integration test.
Here's the code:
var q = require("q"),
request = require("request");
/*
Example of service wrapper that makes HTTP request.
*/
function getProducts() {
var deferred = q.defer();
request.get({uri : "http://localhost/some-service" }, function (e, r, body) {
deferred.resolve(JSON.parse(body));
});
return deferred.promise;
}
/*
The route
*/
exports.getProducts = function (request, response) {
getProducts()
.then(function (data) {
response.write(JSON.stringify(data));
response.end();
});
};
I want to test that all the components work together but with a fake HTTP response, so I am creating a stub for the request/http interactions.
I am using Chai, Sinon and Sinon-Chai and Mocha as the test runner.
Here's the test code:
var chai = require("chai"),
should = chai.should(),
sinon = require("sinon"),
sinonChai = require("sinon-chai"),
route = require("../routes"),
request = require("request");
chai.use(sinonChai);
describe("product service", function () {
before(function(done){
sinon
.stub(request, "get")
// change the text of product name to cause test failure.
.yields(null, null, JSON.stringify({ products: [{ name : "product name" }] }));
done();
});
after(function(done){
request.get.restore();
done();
});
it("should call product route and return expected resonse", function (done) {
var writeSpy = {},
response = {
write : function () {
writeSpy.should.have.been.calledWith("{\"products\":[{\"name\":\"product name\"}]}");
done();
}
};
writeSpy = sinon.spy(response, "write");
route.getProducts(null, response);
});
});
If the argument written to the response (response.write) matches the test passes ok. The issue is that when the test fails the failure message is:
"Error: timeout of 2000ms exceeded"
I've referenced this answer, however it doesn't resolve the problem.
How can I get this code to display the correct test name and the reason for failure?
NB A secondary question may be, could the way the response object is being asserted be improved upon?
The problem looks like an exception is getting swallowed somewhere. The first thing that comes to my mind is adding done at the end of your promise chain:
exports.getProducts = function (request, response) {
getProducts()
.then(function (data) {
response.write(JSON.stringify(data));
response.end();
})
.done(); /// <<< Add this!
};
It is typically the case when working with promises that you want to end your chain by calling a method like this. Some implementations call it done, some call it end.
How can I get this code to display the correct test name and the reason for failure?
If Mocha never sees the exception, there is nothing it can do to give you a nice error message. One way to diagnose a possible swallowed exception is to add a try... catch block around the offending code and dump something to the console.

Resources