Cloud Function writing to Firebase Realtime Database times out [duplicate] - javascript

This question already has an answer here:
Cloud Functions for Firebase HTTP timeout
(1 answer)
Closed 5 years ago.
I can see a new entry is added as soon as I get the URL, but the request hangs and times out. Why?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.add = functions.https.onRequest((request, response)=>
{
var ref = admin.database().ref("jobs");
var childRef = ref.push();
childRef.set
(
{
title: "test",
pay: 100
}
);
})
The code is based on the following example. https://firebase.google.com/docs/database/admin/save-data
Result
{"error":{"code":500,"status":"INTERNAL","message":"function execution attempt timed out"}}

Cloud Functions triggered by HTTP requests need to be terminated by ending them with a send(), redirect(), or end(), otherwise they will continue running and reach the timeout.
From the terminate HTTP functions section of the documentation on HTTP triggers:
Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system. See also Sync, Async and Promises.
Therefore, in your example, you can end the request by sending a response:
exports.add = functions.https.onRequest((request, response)=>
{
var ref = admin.database().ref("jobs");
var childRef = ref.push();
childRef.set
(
{
title: "test",
pay: 100
}
);
response.status(200).send("OK!");
})

Related

Terminate asynchronous firebase-function properly [duplicate]

This question already has an answer here:
Why is my PDF not saving intermittently in my Node function?
(1 answer)
Closed last year.
As described in the firebase docs, it is required to
"resolve functions that perform asynchronous processing (also known as
"background functions") by returning a JavaScript promise."
(https://firebase.google.com/docs/functions/terminate-functions?hl=en).
otherwise it might happen, that
"the Cloud Functions instance running your function does not shut down
before your function successfully reaches its terminating condition or
state. (https://firebase.google.com/docs/functions/terminate-functions?hl=en)
In this case I am trying to adapt a demo-code for pdf-generation written by Volodymyr Golosay on https://medium.com/firebase-developers/how-to-generate-and-store-a-pdf-with-firebase-7faebb74ccbf.
The demo uses 'https.onRequest' as trigger and fulfillis the termination requirement with 'response.send(result)'. In the adaption I need to use a 'document.onCreate' trigger and therefor need to find a different termination.
In other functions I can fulfill this requirement by using async/await, but here I am struggling to get a stable function with good performance. The shown function logs after 675 ms "finished with status: 'ok' ", but around 2 minutes later it logs again that the pdf-file is saved now (see screenshot of the logger).
What should I do to terminate the function properly?
// adapting the demo code by Volodymyr Golosay published on https://medium.com/firebase-developers/how-to-generate-and-store-a-pdf-with-firebase-7faebb74ccbf
// library installed -> npm install pdfmake
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
const Printer = require('pdfmake');
const fonts = require('pdfmake/build/vfs_fonts.js');
const fontDescriptors = {
Roboto: {
normal: Buffer.from(fonts.pdfMake.vfs['Roboto-Regular.ttf'], 'base64'),
bold: Buffer.from(fonts.pdfMake.vfs['Roboto-Medium.ttf'], 'base64'),
italics: Buffer.from(fonts.pdfMake.vfs['Roboto-Italic.ttf'], 'base64'),
bolditalics: Buffer.from(fonts.pdfMake.vfs['Roboto-Italic.ttf'], 'base64'),
}
};
exports.generateDemoPdf = functions
// trigger by 'document.onCreate', while demo uses 'https.onRequest'
.firestore
.document('collection/{docId}')
.onCreate(async (snap, context) => {
const printer = new Printer(fontDescriptors);
const chunks = [];
// define the content of the pdf-file
const docDefinition = {
content: [{
text: 'PDF text is here.',
fontSize: 19 }
]
};
const pdfDoc = printer.createPdfKitDocument(docDefinition);
pdfDoc.on('data', (chunk) => {
chunks.push(chunk);
});
pdfDoc.on('end', async () => {
const result = Buffer.concat(chunks);
// Upload generated file to the Cloud Storage
const docId = "123456789"
const bucket = admin.storage().bucket();
const fileRef = bucket.file(`${docId}.pdf`, {
metadata: {
contentType: 'application/pdf'
}
});
await fileRef.save(result);
console.log('result is saved');
// NEEDS PROPER TERMINATION HERE?? NEEDS TO RETURN A PROMISE?? FIREBASE DOCS: https://firebase.google.com/docs/functions/terminate-functions?hl=en
// the demo with 'https.onRequest' uses the following line to terminate the function properly:
// response.send(result);
});
pdfDoc.on('error', (err) => {
return functions.logger.log('An error occured!');
});
pdfDoc.end();
});
I think everything is fine in your code. It seems it takes 1m 34s to render the file and save it to storage.
Cloud function will be terminated automatically when all micro and macro tasks are done. Right after you last await.
To check how long does it takes and does it terminate right after saving, you can run the firebase emulator on your local machine.
You will see logs in the terminal and simultaneously watch on storage.
I suspect you did terminate properly - that's the nature of promises. Your function "terminated" with a 200 status, returning a PROMISE for the results of the PDF save. When the PDF save actually terminates later, the result is logged and the promise resolved. This behavior is WHY you return the promise.

How to set MAX_ATTEMPTS from code to Google Cloud Task?

How to set MAX_ATTEMPT of the tasks in google cloud queue in code?
When I create new task, I want to set how many repetitions of a given task should be, can I do it from the code below?
I have google cloud queue like here:
const {CloudTasksClient} = require('#google-cloud/tasks');
const client = new CloudTasksClient();
async function createHttpTask() {
const project = 'my-project-id';
const queue = 'my-queue';
const location = 'us-central1';
const url = 'https://example.com/taskhandler';
const payload = 'Hello, World!';
const inSeconds = 180;
const parent = client.queuePath(project, location, queue);
const task = {
httpRequest: {
httpMethod: 'POST',
url,
},
};
if (payload) {
task.httpRequest.body = Buffer.from(payload).toString('base64');
}
console.log('Sending task:');
console.log(task);
const request = {parent: parent, task: task};
await client.createTask(request);
}
createHttpTask();
From the Google Cloud Documentation i see that I can do it from the console, for the whole queue - https://cloud.google.com/tasks/docs/configuring-queues#retry , but I want to set this dynamically for the tasks
thanks for any help!
Unfortunately, the answer is no. You can not set retry parameters on individual tasks. I make this claim by looking at this document:
REST Resource: projects.locations.queues
which is the REST API for creating task queues. In there, under the documentation for retryConfig we read:
For tasks created using Cloud Tasks: the queue-level retry settings apply to all tasks in the queue that were created using Cloud Tasks. Retry settings cannot be set on individual tasks.

How can I get a 'get' request to run on a schedule in NodeJS?

The function I would like this function to run by itself at time intervals. As it is now I have to visit the '/getCompanyInfo' path to trigger it. I would like it to run every minute as if I was visiting the '/getCompanyInfo' path each minute. The app is on Heroku and I would like the function to execute without any pages open.
The original function that is triggered by visiting the path.
const express = require('express');
const app = express();
/**
* getCompanyInfo ()
*/
app.get('/getCompanyInfo', function(req,res){
const companyID = oauthClient.getToken().realmId;
console.log(companyID)
const url = OAuthClient.environment.production ;
oauthClient.makeApiCall({url: url + 'v3/company/0000000000/salesreceipt/8?minorversion=41'})
.then(function(authResponse){
console.log("The response for API call is :"+JSON.parse(JSON.stringify(authResponse)));
res.send(authResponse);
})
.catch(function(e) {
console.error(e);
});
});
One of my attempts here was to put it in a function that executes each minute using node-schedule.
This one doesn't do anything other than print 'This will run once a minute.' to the console.
I tried removing
app.get(function(req,res){
and the
})
below it but that made the app (hosted on Heroku) fail to build.
const express = require('express');
const app = express();
var schedule = require('node-schedule');
var j = schedule.scheduleJob('* * * * *', function(){
console.log('This will run once a minute.');
app.get(function(req,res){
const companyID = oauthClient.getToken().realmId;
console.log(companyID)
const url = OAuthClient.environment.production ;
oauthClient.makeApiCall({url: url + 'v3/company/0000000000/salesreceipt/8?minorversion=41'})
.then(function(authResponse){
console.log("The response for API call is :"+JSON.parse(JSON.stringify(authResponse)));
res.send(authResponse);
})
.catch(function(e) {
console.error(e);
});
});
});
More Context:
It is inside an app I have on Heroku. I would like to set the app to make a requests for JSON data from the API every x time without me having to touch it.
app.get initializes api handler - e.g. this is your api route definition - the thing that will respond when you call GET /getCompanyInfo via web browser or some other client. You should not redefine it regularly with your scheduled action.
The failed build after you've removed the route handler is probably because of the res.send(authResponse); left behind.
You could have something like:
// function that will be used to get the data
const getCompanyInfo = (done) => {
const companyID = oauthClient.getToken().realmId
console.log(companyID)
const url = OAuthClient.environment.production
oauthClient.makeApiCall({url: url + 'v3/company/0000000000/salesreceipt/8?minorversion=41'})
.then((authResponse) => {
console.log("The response for API call is :"+JSON.parse(JSON.stringify(authResponse)))
done(authResponse)
})
.catch((e) => {
console.error(e)
})
}
// this will trigger the function regularly on the specified interval
const j = schedule.scheduleJob('* * * * *', () => {
getCompanyInfo((companyInfo) => {
// ...do whatever you want with the info
})
})
// this will return you the data by demand, when you call GET /getCompanyInfo via browser
app.get('/getCompanyInfo', function(req,res) {
getCompanyInfo((companyInfo) => {
res.send(companyInfo)
})
})
Heroku has an add on called Heroku Scheduler that does what you want. The node-schedule npm package might do the job, but as you mentioned, you probably aren't going to be able to see the execution/results/logs of your jobs that run every 24 hours without making some interface for it on your own.
For your issue, calling app.get doesn't make a lot of sense. That's just telling node about the route. Assuming you have your /getCompanyInfo route up and running, you just need to call it in your scheduled job, not re-register it every time.
You could also just do this (http being the http client you're using):
var j = schedule.scheduleJob('* * * * *', async function(){
console.log('This will run once a minute.');
const result = await http.get('/getCompanyInfo');
console.log(result);
});

ReferenceError when trying to send notification with Firebase via Node.js

So I am trying to send a notification via Functions on Firebase.
I am doing the notification programming on JavaScript via Node.js.
Upon clicking Send Friend Request from one account, the other person is suppose to get a notification as specified on the payload of the JavaScript file I have attached below.
I keep getting the following error on my Firebase Functions
ReferenceError: event is not defined.
Here is an image of the exact error.
Here is my JavaScript file:
/*
* Functions SDK : is required to work with firebase functions.
* Admin SDK : is required to send Notification using functions.
*/
//This runs JavaScript in Strict Mode, which prevents the use of things such as undefined variables.
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/*
* 'OnWrite' works as 'addValueEventListener' for android. It will fire the function
* everytime there is some item added, removed or changed from the provided 'database.ref'
* 'sendNotification' is the name of the function, which can be changed according to
* your requirement
*/
exports.sendNotification = functions.database.ref('/Notifications/{retrieveUserId}/{notificationId}').onWrite((data, context) => {
/*
* You can store values as variables from the 'database.ref'
* Just like here, I've done for 'user_id' and 'notification'
*/
const retrieveUserId = context.params.retrieveUserId;
const notificationId = context.params.notificationId;
console.log('User id is : ', retrieveUserId);
//Prevents notification being sent if there are no logs of notifications in the database.
if (!event.data.val()) {
return console.log('A notification has been deleted from the database : ', notificationId);
}
const deviceToken = admin.database().ref(`/Users/${retrieveUserId}/device_token`).once('value');
return deviceToken.then(result => {
const tokenId = result.val();
const payload = {
notification: {
title: "Friend Request",
body: "You have received a friend request from Slim Shady",
icon: "default"
}
};
return admin.messaging().sendToDevice(tokenId, payload).then(response => {
return console.log('Notification was sent to the user');
});
});
});
This is a picture of parents and children of my Firebase database referred to in the JavaScript file.
As the error states an event not being defined, I'm trying to figure out which event I have not defined.
What is the issue here?
You have not defined event in this code block:
if (!event.data.val()) {

How to return API response to conversation in Dialogflow Webhook

I have a Dialogflow Webhook fulfillment integration, which does a GET request to an API i have set up. But i can't seem to get the API's response as text in my conversation.
The API does receive a http GET request and returns a response with statuscode 200.
If i do the same request in my browser this is the result:
{
"avmid": "1011GZ 18",
"straat": "Snoekjesgracht",
"postcode": "1011GZ",
"stad": "AMSTERDAM",
"provincienaam": "Noord-Holland",
"date": "2013-12-30",
"koopsom": 199800,
"koopsom2018q2": 333849
}
I have tried several things but i don't seem to be able to get it to work.
This is my JavaScript:
'use strict';
const http = require('http');
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function address_api_request (agent) {
let postcode = agent.parameters.zipcode;
let housenumber = agent.parameters.housenumber;
let avmid = postcode.toString()+'+'+housenumber.toString();
let url = 'http://XXX.XXX.XXX.XXX:XX/api/1011GZ+18';
var http = require('http');
http.get(url, function(response) {
var body = '';
response.on('data', function(d) {
body += d;
});
response.on('end', function() {
var parsed = JSON.parse(body);
agent.add(parsed.toString());
});
});
}
function welcome (agent) {
agent.add(`Welcome to my agent!`);
agent.add(agent.request_.body.queryResult.fulfillmentText);
}
function fallback (agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('company.woningwaarde-get-address-api', address_api_request);
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
The intents work, and if i replace the address_api_request function with the underneath code it returns "test:
function address_api_request (agent) {
agent.add('test');
}
There are two issues here.
The first is that if you are using Dialogflow's built-in editor, it is using Firebase Cloud Functions under the covers. The default level of Firebase Cloud Functions does not allow network access outside of Google's cloud.
You can resolve this by upgrading the project to Firebase's Blaze Plan, which does require a credit card on file and charges per-use, however there is a free tier which is more than sufficient for reasonable testing, and even some light use under production. Once your action has been approved, you'll be eligible to receive Google Cloud credits which may be used for this purpose.
The other problem is that you have an asynchronous function (the http.get()), but you aren't using a Promise to handle the function and let the handleRequest() method know that it needs to wait for the function to resolve before returning a result. If you are using async functions, the Dialogflow library requires that you return a Promise from the function.
You have a few choices for how to handle this. First, you can wrap your call to http.get() as part of creating a new Promise object and in your end handler, send the message as you've indicated and then call the resolve parameter that you need to accept in the Promise handler. Easier, however, would be to use a library such as request-promise-native which wraps much of this for you and lets you get a result as part of a then() clause, where you would then handle it.
I haven't tested it, but your code might then look something like this:
function address_api_request (agent) {
let postcode = agent.parameters.zipcode;
let housenumber = agent.parameters.housenumber;
let avmid = postcode.toString()+'+'+housenumber.toString();
let url = 'http://XXX.XXX.XXX.XXX:XX/api/1011GZ+18';
const rp = require('request-promise-native');
var options = {
uri: url,
json: true
};
return rp( options )
.then( body => {
// Since json was set true above, it parses it for you
// You wouldn't really want to send back the whole body
agent.add( body );
})
.catch( err => {
console.log('Problem with request', err );
agent.add( "Uh oh, something went wrong" );
});
}

Categories