Update value once write completes in Cloud Function - node.js

I'm trying to update one value after a write completes (in a Cloud Function) but it just wont work (I'm sure this is a stupidly simple problem). Code below:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const firebase = require('firebase');
admin.initializeApp(functions.config().firebase);
exports.createMessage = functions.https.onRequest((request, response) => {
const json = JSON.parse(request.query.json); // == "{'start':0, 'end':0}"
json.start = firebase.database.ServerValue.TIMESTAMP;
admin.database().ref('/messages/').push(json).then(snapshot => {
//Here is the problem. Whatever I try here it won't work to retrieve the value.
//So, how to I get the "start" value, which has been written to the DB (TIMESTAMP value)?
var startValue = snapshot.ref.child('start').val();
snapshot.ref.update({ end: (startValue + 85800000) }).then(snapshot2=>{
response.redirect(303, snapshot.ref);
});
});
});
Is the problem that I'm using admin.database()?

This code:
var startValue = snapshot.ref.child('start').val();
doesn't actually retrieve any values. Take a look at the docs for DataSnapshot. Reach into that snapshot directly with child() - you don't need the ref. Maybe this is what you meant?
var startValue = snapshot.child('start').val();

I'm not sure if there's a bug in Firebase or if I'm using it wrong, but if I try to call any method on the snapshot-reference I will only get an error saying: TypeError: snapshot.xxx is not a function where xxx is the function name i try to use (for example: child(...), forEach(...), etc).
However, the following seems to fix the issue with the snapshot:
admin.database().ref('/messages/').push(json).once('value').then(snapshot => {
instead of:
admin.database().ref('/messages/').push(json).then(snapshot => {
My uneducated guess is that the then-promise, for the push-function returns some faulty snapshot since the only thing that seems to work is snapshot.key.
Also, if I'm not mistaken, doesn't my solution make two reads now, instead of one? Since push will write and then (supposedly) read and return the written value and then I read it once more with once(value).
Does anyone has any further insights into this problem?

Related

How to listen to realtime database changes (like a stream) in firebase cloud functions?

I am trying to listen to the changes in a specific field in a specific document in Firebase Realtime database. I am using Node JS for the cloud functions. this is the function.
const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp(functions.config.firebase);
const delay = ms => new Promise(res => setTimeout(res, ms));
exports.condOnUpdate = functions.database.ref('data/').onWrite(async snapshot=> {
const snapBefore = snapshot.before;
const snapAfter = snapshot.after;
const dataB = snapBefore.val();
const data = snapAfter.val();
const prev= dataB['cond'];
const curr= data['cond'];
// terminate if there is no change
if(prev==curr)
{
return;
}
if(curr==0){
// send notifications every 10 seconds until value becomes 1
while(true){
admin.messaging().sendToTopic("all", payload);
await delay(10000);
}
}else if(curr==1){
// send one notification
admin.messaging().sendToTopic("all", payload);
return;
}
});
The function works as expected but the loop never stops as it never exists the loop, instead, the function runs again (with new instance I suppose).
so is there any way to listen to the data changes in one function just like streams in other languages, or perhaps stop all cloud functions from running.
Thanks in advance!
From the comments it seems that the cond property is updated from outside of the Cloud Function. When that happens, it will trigger a new instance of the Cloud Function to run with its own curr variable, but it won't cause the curr value in the current instance to be updated. So the curr variable in the original instance of your code will never become true, and it will continue to run until it times out.
If you want the current instance to detect the change to the property, you will need to monitor that property inside the code too by calling onValue on a reference to it.
An easier approach though might be to use a interval trigger rather than a database trigger to:
have code execute every minute, and then in there
query the database with the relevant cond value, and then
send a notification to each of those.
This requires no endless loop or timeouts in your code, which is typically a better approach when it comes to Cloud Functions.

How to pass variable on button click in Inline Keyboard in Telegraf?

I found this solution that helps to solve my problem:
function makeMenu(clientId){
let clientButton = [Markup.callbackButton('📗 '+clientId+' Information', 'info-'+clientId)]
return Markup.inlineKeyboard([clientButton])
}
bot.action(/^[client]+(-[a-z]+)?$/, ctx => {
console.log(ctx.match[1].split('-')[1] )
})
But this is a poor solution or a workaround because I need to pass a long list of parameters and there is a limitation in the telegram's api to pass strings up to 64 bytes.
One solution to the issue you're facing would be to put the large data inside a database and passing an Id (or a ref to that data) as the callback data and using the code you've posted.
An example code would be:
function makeMenu(clientId){
const id = storeDataToDB('info-'+clientId) // store the large data to DB in here
let clientButton = [Markup.callbackButton('📗 '+clientId+' Information', id)]
return Markup.inlineKeyboard([clientButton])
}
bot.action(/^[client]+(-[a-z]+)?$/, ctx => {
const data = getDataFromDB(ctx.match[1].split('-')[1]) // fetch the data from DB and continue..
console.log(data)
})
You could use firebase, mongodb, or any other DB.. (just make sure the ID adheres to the limit imposed by telegram)

lrange returns false instead of the list

I'm trying to make my own simple Message Queue using Redis.
However, I'm having problem making a queue with Redis.
I used Redis for caching in other parts of my project so i'm sure redis connection is fine(+ I tried priting out the instance and it seems fine).
message_queue.js
const redis = require("./redis.js");
var sendMessage = async (queue) => {
var result = await redis.rpush(queue,5);
console.log(result);
var arr = await redis.lrange(queue,0,-1);
console.log(arr);
};
redis.js
const redis = require('redis');
const redisinfo = require('../secret/redisinfo.js');
const client = redis.createClient(redisinfo);
client.on("error",function(err){
console.log("Error " + err);
});
module.exports = client;
when i run sendMessage function from message_queue.js,
it prints false and false.
what am i doing wrong to pushing item into queue and printing it? Do I need to do something before this process such as declaring a list?
p.s.
I don't know if this is the proper method to use this package but it seems like i need to use a callback function to access the result...
this following code works as expected
redis.lrange(queue,0,-1,function(err,res){
if(res.length != 0){
console.log(`Something is in queue`);
}
});
You don't need to "declare" lists in Redis, RPUSH should create a list under the specified key the first time it is used.
Is it possible that you have already used the key name and stored something that is not a list? That would explain the behavior you are seeing. Run DEL <key> first in Redis to fix it, in that case.

Firebase cloud function always getting null snapshot

I'm experiencing some problems while testing my firebase cloud function in the interactive shell (firebase experimental:functions:shell). Within the onWrite event, event.data.val() returns null when I first call it, instead of returning the information I expect. Also, after that, it's not automatically called again after I change some data through a mobile app.
Here is a sample code:
"use strict";
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.syncMySql = functions.database.ref('/something/{user_token}/{something_id/}')
.onWrite(event => {
const snapshot = event.data.val();
const user_token = event.params.user_token;
const mock_id = event.params.something_id;
console.log(functions.config());
console.log('# User: ' + user_token + ',\n# Something: ' + something_id + ',\n# Snapshot: ' + snapshot + '\n Evento: ' + JSON.stringify(event));
return;
});
And here is a sample of how I'm trying to call it from firebase experimental:functions:shell:
syncMySql('', {params: {user_token: 'sOmEUseRtoKen1234-i_', something_id: '456789'}})
Any ideas on what am I doing wrong?
Also, what is this first parameter called in syncMySql? What's it's purpose?
For invoking functions via the test environment like this, you need to actually provide the data that was supposedly written to the DB yourself. event.data.val() won't read the data from an exising entitity based on the params, but will only return the information you passed in the first parameter of the function call.
For invoking onWrite and onUpdate functions you need to provide the data in the form {before: 'old_data', after: 'new_data' }, where old_data and new_data can be primitives and/or objects, i.e. anything you would write to your DB.
So your full call should be something like this:
syncMySql(
{
before: null,
after: 'something'
},
{params: {user_token: 'sOmEUseRtoKen1234-i_', something_id: '456789'}}
);
In this case event.data.val() should return 'something'.

Firebase TypeError: ref.transaction is not a function

I have an issue with firebase database functions. The documentation contains the following minimal example to work with transactional data:
var upvotesRef = db.ref("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.transaction(function (current_value) {
return (current_value || 0) + 1;
});
Now, my firebase database structure looks like the following
/game-results
/RaUALJvTZVea5y6h1lzqGJPMpuI3
/distance
/score
/timestamp
/3ItAqKHL0XRUOum2Ajdz9hHQxMs1
/distance
/score
/timestamp
/statistics
/total-distance
/total-score
And I would like to sum up the scores and distances to a total. I'm using the following function, here for total-distance:
const functions = require('firebase-functions');
exports.updateTotalDistance = functions.database.ref('/game-results/{userId}/distance')
.onWrite(event => {
const newDistance = event.data.val();
const distanceRef = functions.database.ref('/statistics/total-distance')
return distanceRef.transaction(function(currentDistance) {
console.log('currentDistance', currentDistance);
return (currentDistance || 0) + newDistance;
});
});
However, I can not wrap my head around why I get the following error in the firebase function logs:
TypeError: distanceRef.transaction is not a function
at exports.updateTotalDistance.functions.database.ref.onWrite.event (/user_code/index.js:19:22)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
Why is the Database reference not allowing me to write transactional data? All I want to do is to sum up all scores and distances.
functions.database.ref is not what you are looking for, that's the builder for listeners. You'll want to use event.data.ref or admin.database().ref
but you've got a detail wrong. Maybe this will help some other people:
it's not admin.database.ref('')
it's admin.database().ref('')

Resources