I'm trying to run a cloud function and change a value on the database, but every time I return a promise with or without 'firebase-admin' module the function times out after 60 seconds.
Here is my code:
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.handleDisconnection = functions.database.ref('/pages/{pageId}/listeners/disconnection')
.onWrite(event => {
const eventSnapshot = event.data;
const isDisconnected = eventSnapshot.val();
const pageId = event.params.pageId;
console.log('there is a new disconnection on page ' + pageId + ', isDisconnected: ' + isDisconnected);
if (eventSnapshot.changed() && isDisconnected) {
console.log('is disconnected true');
return admin.database().ref('/pages/'+pageId+'/listeners/disconnection').set({ disconnection: false }).then(() => {
console.log('Write succeeded!'); // this never triggers
});
}
});
if (eventSnapshot.changed() && isDisconnected) {
console.log('is disconnected true');`
return admin.database.ref('/...')
.set({ disconnection: false }, err => {
if(!err) // No error
{ console.log("Set Updated"); }
});
set Methods Has a callback which passes in err as object
you can use err to get the operations status.
Turns out the problem was not in the code.
Solved it updating Node.js & npm
Use the "Current" version https://nodejs.org/en/
Related
I am trying to set up cloud functions with firebase and I am having a slightly difficult time getting it set up.
I want to set up a function that gets called by an HTTP request. The function would take the information provided, double-check if those values are indeed the same values as the ones found in my firestorm
and then execute some Javascript code before responding; this is my code:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require("firebase-functions");
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// [START trigger]
exports.buyCrypto = functions.https.onRequest((request, res) =>
{
// [END trigger]
// [START sendError]
// Forbidding PUT requests.
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
// [END sendError]
// [START readQueryParam]
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if(crypto === "BTC")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
if(crypto === "ETH")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
// Push the new message into Firestore using the Firebase Admin SDK.
//const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
// [START sendResponse]
const formattedResponse = "IDK"
return res.status(403).send("Failed");
// [END sendResponse]
});
Unfortunatly I cannot seem to find a great deal of documentation for firebase functions and when I try to test it with the emulator through a web browser it goes into infinite loading and does not display an error message so I am finding it impossible to debug anything.
Thank you for your time
You are calling return res.status(403).send("Failed"); outside of the then() block, so this line will be called before the asynchronous call to the get() method is completed and the Promise returned by this method is fulfilled. Result: your Cloud Function always sends back an error to its caller.
In addition, you do doc.data.btc instead of doc.data().btc. See the doc for the DocumentSnapshot, data() is a method.
Also, note that you don't need to use return in an HTTPS Cloud Function. Just send back a response with res.redirect(), res.send(), or res.end(). You may watch this video: https://www.youtube.com/watch?v=7IkUgCLr5oA.
The following should therefore do the trick:
exports.buyCrypto = functions.https.onRequest((request, res) => {
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if (crypto === "BTC") {
if (doc.data().btc <= amount) {
//execute buy
res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else if (crypto === "ETH") {
if (doc.data.btc <= amount) {
//execute buy
return res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
console.log("No such document!");
// send a 200 response or throw an error res.status(200).send("....");
}
}).catch((error) => {
console.log("Error getting document:", error);
res.status(500).send(error);
});
});
I have been asked to create a firebase (fb) function that triggers onUpdate. Then I have to gather a bunch of information from the fb database and publish a message so that another function triggers at that point.
fb update triggers functionA
functionA publishes a message
functionB is a subscriber to that topic and is triggered after the message publishes.
I have the basics of the onUpdate trigger below:
const functions = require("firebase-functions"),
Promise = require("promise"),
PubSub = require(`#google-cloud/pubsub`),
admin = require("firebase-admin");
const pubsub = new PubSub();
exports.checkInOrder = functions.database
.ref("/orders/{id}")
.onUpdate((change, context) => {
const after = change.after.val();
// check the status: "pending-pickup" or "fulfilled" TODO
if (after.status === "new") {
console.log("ended because package is new.");
return null;
}
let dsObj = {};
const orderId = context.params.id;
const topicName = 'check-in-order';
const subscriptionName = 'check-in-order';
return // how would I send the message to the pubsub here?
});
So to summarize:
How do I send a message to pubsub
How do I subscribe a firebase function to trigger when a topic receives a message?
If it sounds very confusing I'm sorry - I am completely lost here. Thanks!
So I finally figured it out. Pretty straight forward, just felt overwhelmed with learning all of this stuff at once and in a rushed time frame. Below is my code. I included the whole page so in case this might help someone else out there in the future.
const functions = require("firebase-functions"),
Promise = require("promise"),
PubSub = require(`#google-cloud/pubsub`),
admin = require("firebase-admin");
const init = () => {
const topicName = "check-in-order";
pubsub
.createTopic(topicName)
.then(results => {
const topic = results[0];
console.log(`Topic ${topicName} created.`);
return;
})
.catch(err => {
console.error("ERROR on init:", err);
return;
});
};
const pubsub = new PubSub();
exports.orderCreated = functions.database
.ref("/orders/{id}")
.onUpdate((change, context) => {
console.log("it's happening!");
const after = change.after.val();
console.log("after::::>", after)
if (after.status === "new") {
console.log('stopped because status is new');
return null;
}
if (after.status === "pending-pickup" || after.status === "fulfilled") {
const orderId = context.params.id;
const topicName = "check-in-order";
let { assignedSlot, userId, parcelLockerId, carrier, trackingNumber, orderInDate, pickUpCode, status, } = after;
const dsObj = {
order: {
orderId,
userId,
parcelLockerId,
carrier,
trackingNumber,
orderInDate,
pickUpCode,
status,
}
};
const dataBuffer = new Buffer.from(dsObj.toString());
// publish to trigger check in function
return pubsub
.topic(topicName)
.publisher()
.publish(dataBuffer)
.then(messageId => {
console.log(`:::::::: Message ${messageId} has now published. :::::::::::`);
return true;
})
.catch(err => {
console.error("ERROR:", err);
throw err;
});
}
return false;
});
exports.checkInOrder = () => {
}
exports.checkIn = functions.pubsub.topic('check-in-order').onPublish((message) => {
console.log("everything is running now", message);
return true;
});
init();
I could need some help setting up the node.js api from mollie with firebase cloud functions. I tried to use parts of the cloudfunction setting up paypal guide but didn't get it to work yet. I'm new to cloud functions and node.js so i have a hard time getting it done. I am using hard coded payment properties for testing purposes. I am using a blaze subscribtion to be able to do requests to non-Google services The code i have so far:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
Mollie = require("mollie-api-node");
mollie = new Mollie.API.Client;
mollie.setApiKey("test_GhQyK7Gkkkkkk**********");
querystring = require("querystring");
fs = require("fs");
exports.pay = functions.https.onRequest((req, res) => {
console.log('1. response', res)
mollie.payments.create({
amount: 10.00,
method: Mollie.API.Object.Method.IDEAL,
description: "My first iDEAL payment",
redirectUrl: "https://dummyse-afgerond",
webhookUrl: "https://us-c90e9d.cloudfunctions.net/process",
testmode: true
}, (payment)=> {
if (payment.error) {
console.error('errrr' , payment.error);
return response.end();
}
console.log('3. payment.getPaymentUrl()', payment.getPaymentUrl());
res.redirect(302, payment.getPaymentUrl());
});
});
exports.process = functions.https.onRequest((req, res) => {
let _this = this;
this.body = "";
req.on("data", (data)=> {
console.log('_this.body += data', _this.body += data)
return _this.body += data;
});
req.on("end", ()=> {
console.log('hier dan?')
let mollie, _ref;
_this.body = querystring.parse(_this.body);
if (!((_ref = _this.body) !== null ? _ref.id : void 0)) {
console.log('res.end()', res.end())
return res.end();
}
})
mollie.payments.get(
_this.body.id
, (payment) => {
if (payment.error) {
console.error('3a. err', payment.error);
return response.end();
}
console.log('4a. payment', payment);
console.log('5a. payment.isPaid()', payment.isPaid());
if (payment.isPaid()) {
/*
At this point you'd probably want to start the process of delivering the
product to the customer.
*/
console.log('6a. payment is payed!!!!!!!!!!')
} else if (!payment.isOpen()) {
/*
The payment isn't paid and isn't open anymore. We can assume it was
aborted.
*/
console.log('6a. payment is aborted!!!!!!!!!!')
}
res.end();
});
});
this is mollies api guide:
https://github.com/mollie/mollie-api-node
this is paypal cloud function guide:
https://github.com/firebase/functions-samples/tree/master/paypal
UPDATE:
I updated the code. The error i get now is that all properties of the payment variable are undefined in the procces function (webhook). and the payment.isPaid() function says false while it should say true.
I did the same when I first tried to get mollie working in firebase cloud functions, this:
let _this = this;
this.body = "";
req.on("data", (data)=> {
console.log('_this.body += data', _this.body += data)
return _this.body += data;
});
req.on("end", ()=> {
console.log('hier dan?')
let mollie, _ref;
_this.body = querystring.parse(_this.body);
if (!((_ref = _this.body) !== null ? _ref.id : void 0)) {
console.log('res.end()', res.end())
return res.end();
}
})
is not necessary.
my webhook endpoint is much simpler, directly using the request & response that functions provides:
exports.paymentsWebhook = functions.https.onRequest((request, response) => {
// ...
console.log("request.body: ", request.body);
console.log("request.query: ", request.query);
mollie.payments.get(request.body.id, function (payment) {
console.log("payment", payment);
if (payment.error) {
console.error('payment error: ', payment.error);
response.end();
}
//checking/processing the payment goes here...
response.end();
});
});
I have observed this behavior occasionally with both onCreate and onDelete triggers.
Both the executions happened for the same document created in firestore. There's only one document there so I don't understand how it could trigger the handler twice. the handler itself is very simple:
module.exports = functions.firestore.document('notes/{noteId}').onCreate((event) => {
const db = admin.firestore();
const params = event.params;
const data = event.data.data();
// empty
});
this doesn't happen all the time. What am I missing?
See the Cloud Firestore Triggers Limitations and Guarantees:
Delivery of function invocations is not currently guaranteed. As the
Cloud Firestore and Cloud Functions integration improves, we plan to
guarantee "at least once" delivery. However, this may not always be
the case during beta. This may also result in multiple invocations
for a single event, so for the highest quality functions ensure that
the functions are written to be idempotent.
There is a Firecast video with tips for implementing idempotence.
Also two Google Blog posts: the first, the second.
Based on #saranpol's answer we use the below for now. We have yet to check if we actually get any duplicate event ids though.
const alreadyTriggered = eventId => {
// Firestore doesn't support forward slash in ids and the eventId often has it
const validEventId = eventId.replace('/', '')
const firestore = firebase.firestore()
return firestore.runTransaction(async transaction => {
const ref = firestore.doc(`eventIds/${validEventId}`)
const doc = await transaction.get(ref)
if (doc.exists) {
console.error(`Already triggered function for event: ${validEventId}`)
return true
} else {
transaction.set(ref, {})
return false
}
})
}
// Usage
if (await alreadyTriggered(context.eventId)) {
return
}
In my case I try to use eventId and transaction to prevent onCreate sometimes triggered twice
(you may need to save eventId in list and check if it exist if your function actually triggered often)
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const db = admin.firestore()
exports = module.exports = functions.firestore.document('...').onCreate((snap, context) => {
const prize = 1000
const eventId = context.eventId
if (!eventId) {
return false
}
// increment money
const p1 = () => {
const ref = db.doc('...')
return db.runTransaction(t => {
return t.get(ref).then(doc => {
let money_total = 0
if (doc.exists) {
const eventIdLast = doc.data().event_id_last
if (eventIdLast === eventId) {
throw 'duplicated event'
}
const m0 = doc.data().money_total
if(m0 !== undefined) {
money_total = m0 + prize
}
} else {
money_total = prize
}
return t.set(ref, {
money_total: money_total,
event_id_last: eventId
}, {merge: true})
})
})
}
// will execute p2 p3 p4 if p1 success
const p2 = () => {
...
}
const p3 = () => {
...
}
const p4 = () => {
...
}
return p1().then(() => {
return Promise.all([p2(), p3(), p4()])
}).catch((error) => {
console.log(error)
})
})
Late to the party, I had this issue but having a min instance solved the issue for me
Upon looking #xaxsis attached screenshot, my function took almost the amount of time about 15 seconds for the first request and about 1/4 of that for the second request
I have following sample function from this tutorial: Asynchronous Programming (I Promise!) with Cloud Functions for Firebase - Firecasts
exports.emailEmployeeReport = functions.database
.ref('/employees/${eid}/reports/${rid}')
.onWrite(event => {
const eid = event.params.eid;
const report = event.data.val().report;
const root = event.data.ref.root;
const mgr_promise = root.child(`/employees/${eid}/manager`).once('value');
const then_promise = mgr_promise.then(snap => {
const mgr_id = snap.val();
const email_promise = root.child(`/employees/${mgr_id}/email`).once('value');
return email_promise;
}).catch(reason => {
// Handle the error
console.log(reason);
});;
const then_promise2 = then_promise.then(snap => {
const email = snap.val();
const emailReportPromise = sendReportEmail(email, report);
return emailReportPromise;
}).catch(reason => {
// Handle the error
console.log(reason);
});
return then_promise2;
});
var sendReportEmail = function (email, report) {
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
setTimeout(function () {
try {
var someValue = "sendReportEmail";
console.log(someValue);
// fulfilled
resolve(someValue);
}
catch (ex) {
// rejected
reject(ex);
}
}, 2000);
});
return myFirstPromise;
}
once I run firebase deploy command, eventually I am getting following error:
functions[emailEmployeeReport]: Deploy Error: Failed to configure
trigger
providers/google.firebase.database/eventTypes/ref.write#firebaseio.com
(emailEmployeeReport)
I also have a simple hello-world method and a similar trigger method, and they deploy fine.
Am I missing something here?
The syntax for wildcards in the database reference does not have "$".
Try the following:
exports.emailEmployeeReport = functions.database
.ref('/employees/{eid}/reports/{rid}')