Why my firebase cloud function process never finished? - javascript

I have this function for testing sending notification to all users.
export const sendTestingNotification = functions.https.onRequest((request, response) => {
const message = "Hello world";
const body = "This is body"
const getAllUsersPromise = admin.database().ref('/users').once('value')
const payload = {
notification: {
title: message,
body: body
}
}
return getAllUsersPromise.then(results => {
var tokens : string[] = [];
console.log("Child Snapshot count: ", results.numChildren())
results.forEach(childSnapshot => {
var childData = childSnapshot.val();
var instanceId = String(childData.instanceId)
if (childData.instanceId != null) { tokens.push(instanceId); }
})
console.log('final tokens = ',tokens," notification= ",payload);
return admin.messaging().sendToDevice(tokens, payload).then(response2 => {
console.log ("Done sending notification call. Entering callback.")
const tokensToRemove : string[] = [];
response2.results.forEach ((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to instance id = ', tokens[index], error);
}
else {
console.log("Successfully send notification to ", tokens[index])
}
});
return Promise.all(tokensToRemove);
})
.catch(console.log.bind(console));
})
.catch(console.log.bind(console));
});
I have only one user in firebase database, and that one user has instanceId.
Here's the console log:
Child Snapshot count: 1
final tokens = [ 'eMZHr5WgHmU:APA91bEKg8wAS5qYMxuSJqn...
Done sending notification call. Entering callback.
Successfully send notification to eMZHr5WgHmU:APA91bEKg8wAS5qYMxuSJqn...
Function execution took 60002 ms, finished with status: 'timeout'
What's left of my function execution so that it finishes properly? The browser that call the function never stops show loading indicator.
If I add a response.send before return Promise, the browser loading finished. But checking at the log showed me that the process still working and returned the "Function execution took 60002 ms, finished with status: 'timeout'" error. How can I fix this?

With HTTP type functions, you need to send a response to the caller to terminate the function. For all code paths in your function, you should be calling response.send() or something that will send that response. Please consult the documentation for more information an examples. In particular, read the section on terminating a function.

Related

How to hold a node api execution till a db query does not return null

Seems rather like an unwanted requirement for a piece of code but in my case this is exactly what I need. I have an api (API-1) that interacts with a third party service. This third party service instead of directly giving me a response that I can forward back to frontend is giving me response on API-2 (With a webhook listener endpoint). I'm saving this API-2 response that I get by listening to the webhook in my database. Now I somehow need this response which is now sitting idol in my database in my API-1 so that I can forward it back to the frontend. If I query the database right away during the flow of the API-1 (Just after consume the third party service API), I'll get null as API-2 is getting the response asynchronously with a webhook (Mostly a gap of 1-2 seconds). So I somehow need to figure out an easy way to await/hold the API-1 flow till the database does not return null/returns back the response I saved from API-2 in the database. I'm not sure if the gap will always be 1-2 seconds hence I can't be using setTimeout for this.
//API-1
const sendPaymentRequest = async (req, res) => {
try {
const payment_reponse = await axios.post(url, body, config);
const { data } = payment_reponse;
console.log("Payment request => ", data);
//Check result i.e response from http listener
const webhookResponse = await MpesaModel.findOne({
conversationId: data.ConversationID
});
console.log('Webhook response => ', webhookResponse); //This is null
res.status(200).json({ message: "Send money request", data });
} catch (error) {
console.log("Error while making a payment request", error);
res
.status(400)
.json({ message: "Error while send payment request", error: error.data });
}
};
//API-2 - This is the webhook which receives the response
const saveWebhookB2C = async (req, res) => {
const { Result } = req.body;
//console.log('Mpesa webhook data received => ', Result);
let saveResponse = new MpesaModel({
...Result,
});
const result = await saveResponse.save();
console.log('B2c mpesa to kenya saved in db => ', result);
res.status(200).send();
};
Just wait until the response is different than null:
let webhookResponse = null;
while (response === null) {
await sleep(1000);
webhookResponse = await MpesaModel.findOne({
conversationId: data.ConversationID,
});
}
The sleep function should be fairly simple (and we use it only to wait one second until the next query):
const sleep = (timeInMilliseconds) => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), timeInMilliseconds);
});
};
Note: the while loop will run forever if the answer is always null, so probably you want to add another condition to the while in case the result is always null (maybe a maximum number of tries?).
async function getNotNullResponse({conversationId}){
const webhookResponse = await MpesaModel.findOne({conversationId});
return webhookResponse || getNotNullResponse({conversationId});
}
//API-1
const sendPaymentRequest = async (req, res) => {
try {
const payment_reponse = await axios.post(url, body, config);
const { data } = payment_reponse;
console.log("Payment request => ", data);
//Check result i.e response from http listener
const webhookResponse = await getNotNullResponse({
conversationId: data.ConversationID
});
console.log('Webhook response => ', webhookResponse); //This is not null
res.status(200).json({ message: "Send money request", data });
} catch (error) {
console.log("Error while making a payment request", error);
res
.status(400)
.json({ message: "Error while send payment request", error: error.data });
}
};
//API-2 - This is the webhook which receives the response
const saveWebhookB2C = async (req, res) => {
const { Result } = req.body;
//console.log('Mpesa webhook data received => ', Result);
let saveResponse = new MpesaModel({
...Result,
});
const result = await saveResponse.save();
console.log('B2c mpesa to kenya saved in db => ', result);
res.status(200).send();
};

How to cache a simple javascript array in Cloudflare and return it to Slack?

I am trying to create a Slack bot that will add a developer to a release queue using a CloudFlare worker. Example: /releasenext <your name>, Slack bot will add you to the queue and will tell you who else is in that queue.
I would like to use Cloudflare cache to cache names coming from the POST request. So I am using a javascript array. I am trying to cache that array, and anytime somebody is adding their name to the queue, the Slack bot should reveal the persons already in the queue and the new person who added their name.
So far, I managed to create the communication between Cloudflare and Slack via a command on Slack /releasenext <your name>, and Slack answers me with: the next person to release is: <your name>.
I found the documentation and some examples but I am not understanding how it works exactly.
here is the documentation: https://workers.cloudflare.com/docs/reference/workers-concepts/using-cache/
And here is my code:
const SLACK_TOKEN = "secret"
const BOT_NAME = "Deploy-bot 🤖"
let jsonHeaders = new Headers([["Content-Type", "application/json"]])
let resp
addEventListener('fetch', event => {
event.respondWith(slackWebhookHandler(event.request))
resp = fetch(event.request, { cf: {cacheEverything: true} })
resp = fetch(event.request, { cf: { cacheTtl: 300 } })
})
// simpleResponse generates a simple JSON response
// with the given status code and message.
function simpleResponse(statusCode, message) {
let resp = {
message: message,
status: statusCode
}
return new Response(JSON.stringify(resp), {
headers: jsonHeaders,
status: statusCode
})
}
// slackResponse builds a message for Slack with the given text
// #param {string} text - the message text to return
function slackResponse(text) {
let content = {
response_type: "in_channel",
text: text
}
try {
return new Response(JSON.stringify(content), {
headers: jsonHeaders,
status: 200
})
} catch (e) {
return simpleResponse(
200,
"Sorry, I had an issue generating a response. Try again in a bit!"
)
}
}
// parseMessage parses the selected name from the Slack message.
// #return {string} - the name.
function parseMessage(message) {
try {
const name = message.get("text").trim()
return {name: name}
} catch (e) {
return null
}
}
async function addPersonToQueue(name) {
try {
let cachedResponse = false
if (resp.headers.get("cf-cache-status").toLowerCase() === "hit") {
cachedResponse = true
}
const nameQueue = []
nameQueue.push({name: name}, {cached: cachedResponse})
let output = nameQueue.map(item => {return item.name}).join(', ')
return output
} catch (e) {
throw new Error(`could not fetch the selected name: ${e}`)
}
}
// slackWebhookHandler handles an incoming Slack webhook and generates a response.
// #param {Request} request
async function slackWebhookHandler(request) {
if (request.method !== "POST") {
return simpleResponse(
200,
`Hi, I'm ${BOT_NAME}, a Slack bot for fetching the latest person name to release`
)
}
let formData
try {
formData = await request.formData()
if (formData.get("token").toString() !== SLACK_TOKEN) {
return simpleResponse(403, "Invalid Slack verification token")
}
} catch (e) {
return simpleResponse(400, "could not decode POST form data")
}
try {
let parsed = parseMessage(formData)
if (parsed === null) {
throw new Error("could not parse your message")
}
let reply = await addPersonToQueue(parsed.name)
return slackResponse(
`The next person to release is: *${reply}*`,
`the cache is: ${reply.cached}`
)
} catch (e) {
return simpleResponse(
200,
`Sorry, I had an issue retrieving names from the release queue ${e}`
)
}
}
The error message I have now is
{"message":"Sorry, I had an issue retrieving names from the release queue Error: could not fetch the selected name: TypeError: Cannot read property 'get' of undefined","status":200}
The expected output is: The next person to release is: person1, <your name>.
Assuming that another person already added their name to the queue first.

Cloud function stops executing

So I created a function that will handle Paypal Instant Payment Notification(IPN). All goes well in receiving the notification and I also got "VERIFIED" message when requesting back to Paypal for verification. The problem is that cloud function is not or stops executing inside verification function callback.
exports.ipnHandler = functions.https.onRequest((req, res) => {
console.log("IPN Notification Event Received");
if (req.method !== "POST") {
console.error("Request method not allowed.");
res.status(405).send("Method Not Allowed");
} else {
// Return empty 200 response to acknowledge IPN post success.
console.log("IPN Notification Event received successfully.");
// JSON object of the IPN message consisting of transaction details.
let ipnTransactionMessage = req.body;
// Convert JSON ipn data to a query string since Google Cloud Function does not expose raw request data.
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
// Build the body of the verification post message by prefixing 'cmd=_notify-validate'.
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
console.log(`Verifying IPN: ${verificationBody}`);
let options = {
method: "POST",
url: getPaypalURI(),
body: verificationBody,
};
requestPromise(options)
.then(body => {
// This will log "VERIFIED"
console.log(body); // Cloud function stops here
if (body === "VERIFIED") {
console.log("Manage user subscription");
const transactionType = ipnTransactionMessage.txn_type;
const invoice = ipnTransactionMessage.invoice;
const docRef = firestore.collection("users");
console.log("Transaction type: " + transactionType);
console.log("Invoice " + invoice);
if (transactionType === "subscr_payment") {
console.log("About to subscribe user: " + invoice);
return docRef.where("invoice", "==", invoice).get();
}
}
return console.log("Request completed");
})
.then(snapshots => {
return console.log("Return snapshots " + snapshots);
})
.catch(error => {
console.log(error);
})
res.status(200).end();
}
It starts misbehaving when I added this line return docRef.where("invoice", "==", invoice).get(); Supposedly it will return a snapshot data in the callback below.
.then(snapshots => {
return console.log("Return snapshots " + snapshots);
})
I have solved the problem. It seems that function reached timeout which is 60 seconds in default. I just changed timeout to 5 minutes.

Feathersjs: how to send a response and after that trigger a function?

I had some code working fine for a REST endpoint in which a message was:
created in the database
stepA was processed
when stepA was ok, the response message was returned
stepB was processed.
This was the code:
// POST single message
app.post('/message', (req, res) => {
const url = req.body.properties.url
const image = req.body.properties.image
const extraField = req.body.properties.extraField
db.message.create({
url: url,
image: image,
})
.then(() => myProcess(extraField, 'stepA'))
.then(newMessage => res.json(newMessage))
.then(() => myProcess(extraField, 'stepB'))
})
Now I am trying to have the same using feathersjs, but I do not know how to do 2, 3, 4 exactly.
I have now an AFTER hook for the create method of the message service:
module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
return function processNewMessage (hook) {
const { extraField } = hook.data.properties
Promise.resolve(myProcess(extraField, 'stepA'))
.then( <<NO-IDEA>> ) // Send RESPONSE!!
.then(() => myProcess(extraField, 'stepB'))
return Promise.resolve(hook);
};
};
So my question boils down to: How can I send the response and subsequently trigger 'myProcess stepB' using feathersjs?
Althoug this is 'legacy', I think it might still be relevant.
It is answered in the FAQ of feathersjs!
How to do processing after sending the response to the user:
It depends on the promise that you return in your hook. Here's an example of a hook that sends an email, but doesn't wait for a success message.
function (hook) {
// Send an email by calling to the email service.
hook.app.service('emails').create({
to: 'user#email.com',
body: 'You are so great!'
});
// Send a message to some logging service.
hook.app.service('logging').create(hook.data);
// Return a resolved promise to immediately move to the next hook
// and not wait for the two previous promises to resolve.
return Promise.resolve(hook);
}

How to add results from a promise based API call with message.addReply using Recast.ai?

I'm making a bot that searches restaurants based on location. Can anyone help me why this doesnt show up in FB messenger?:
restaurants(result.getMemory('location').raw)
.then(res=>{
message.addReply(res);
message.reply();
});
}
The call to the restaurants function returns the results from a YELP API call (an array of restaurants) but when I add it as a reply to message, nothing happens in FB messenger.
Here is the full code for message.js:
const recastai = require('recastai');
const restaurants = require('./restaurants');
// This function is the core of the bot behaviour
const replyMessage = (message) => {
// Instantiate Recast.AI SDK, just for request service
const request = new recastai.request(process.env.REQUEST_TOKEN,
process.env.LANGUAGE);
// Get text from message received
const text = message.content;
console.log('I receive: ', text);
// Get senderId to catch unique conversation_token
const senderId = message.senderId;
// Call Recast.AI SDK, through /converse route
request.converseText(text, { conversationToken: senderId })
.then(result => {
//Recast takes text analyses that, returns a result object, generates replies adds messages to reply stack and then sends the replies
//Call Yelp API with when the intent is Location. When Yelp returns result we add it to the result.replies array.
//Then we add everything in result.replies to the messaging queue that sends the responses to FB
if (result.action) {
console.log('The conversation action is: ', result.action.slug);
}
// If there is not any message return by Recast.AI for this current conversation
if (!result.replies.length) {
message.addReply({ type: 'text', content: 'I don\'t have the reply to this yet :)' });
} else {
// Add each reply received from API to replies stack
result.replies.forEach(replyContent => message.addReply({ type: 'text', content: replyContent }));
}
// Send all replies
message.reply()
//send initial reply generated by Recast first
.then(() => {
//call restaurant function that returns a list of results from API
//if the action is location and done
if(result.action && result.action.slug === 'location' && result.action.done){
restaurants(result.getMemory('location').raw)
.then(res=>{
console.log(res);
message.addReply(res);
message.reply();
});
}
})
.catch(err => {
console.error('Error while sending message to channel', err);
});
})
.catch(err => {
console.error('Error while sending message to Recast.AI', err);
});
};
module.exports = replyMessage;
And here is my restaurants.js code that is imported into the message.js file for the bot behavior:
const rp = require('request-promise');
// Load configuration
require('./config');
const restaurants = (location) => {
return Promise.all([
yelpCall(location)
]).then(result => {
//result contains the return value from Yelp call
return result;
});
};
const yelpCall = (location) => {
const auth = {
method: 'POST',
url: 'https://api.yelp.com/oauth2/token?grant_type=client_credentials&client_id='+ process.env.YELP_APP_ID +'&client_secret='+process.env.APP_SECRET
};
return rp(auth)
.then(result => {
const tokens = JSON.parse(result);
return tokens;
})
.then(result=>{
const options = {
url: 'https://api.yelp.com/v3/businesses/search?location=' + location + "&term=thai",
headers: {Authorization: "Bearer " + result.access_token}
};
return rp(options).then(findings =>{
return findings;
});
});
};
module.exports = restaurants;
A few thoughts :
message.reply is thenable, therefore return message.reply() in two places.
request.converseText() is thenable, therefore return request.converseText(...).
restaurants is thenable, therefore return restaurants(...).
in message.js, message.addReply() is passed object of the form {type:..., content:...} in two places but finally just res. Is that correct?
in restaurants.js, Promise.all() appears to be unnecessary. It will cause its result to be wrapped in an array. module.exports = location => yelpCall(location); seems more appropriate.

Categories

Resources