Since the breaking changes in nodejs (moved to nodejs version 8), i having a serious errors and problems with my code. I have looked at google documents how to rewrite the functions but i still can't managed it.
On nodejs version 6 i wrote a function that triggers when a new item is added and then update other nodes in the realtime database
For example
// Keeps track of the length of the 'likes' child list in a separate property.
exports.countlikechange =
functions.database.ref('/likes/{postid}/{userUID}').onWrite(event => {
const collectionRef = event.data.ref.parent;
const model = event.data.val();
let genre = model.genre;
let videoID = model.videoID;
let userVideoID = model.userVideoID;
console.log("model: ",model);
console.log("genre: ",genre);
console.log("videoId: ",videoID);
console.log("userVideoID: ",userVideoID);
const countRef = collectionRef.child('likes');
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
const genreList = admin.database().ref(`${genre}/${videoID}/likes`).transaction(current => {
return (current || 0) + 1;
});
const userList = admin.database().ref(`users/${userVideoID}/likes`).transaction(current => {
return (current || 0) + 1;
});
const videoList = admin.database().ref(`videos/${userVideoID}/${videoID}/likes`).transaction(current => {
return (current || 0) + 1;
});
}
}).then(() => {
console.log('Counter updated.');
return null;
});
});
This func is no longer working because i have update nodejs to version 8.
On google documents the arguments changed, for example:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
Also the return statment has change, it gives me error that i need to use promise.
So im kinda confused, how should i rewrite this func so when it triggers i'll update nodes in the realtime databse.
This doesn't actually have anything to do with the version of node. It has to do with the version of the firebase-functions SDK. You were using a very old pre-release version before. Since 1.0.0, the signatures have changed is a migration guide in documentation that describes the changes. In particular, read this section.
As of v 1.0 of the Firebase SDK for Cloud Functions, the event
parameter for asynchronous functions is obsolete. It has been replaced
by two new parameters: data and context.
You will need to learn the new APIs and port your code.
The requirements for return value have not changed. You are still obliged to return a promise the resolves when all the asynchronous work is complete in your function. If you are seeing a new error message about that, it's because you also upgraded your tools, and they are now checking unhandled promises for you.
Related
Categories:
Database:
I am trying to read the whole selected nodes (categories) and then filter through them and return them in a cloud callable function. How can I get all the data and then filter through it? When I try to log the array, it is empty.
exports.getRecipes = functions.region('europe-west1').https.onCall((data, context) => {
categories = data.categories;
eventsData = [];
for (let i = 0; i < categories.length; i++) {
admin.database().ref(categories[i]).once('value', (data) => {
eventsData.push(data.val());
});
}
console.log(eventsData);
return "hello";
});
Is there any other way getting the whole node with admin.database().ref(), without .once()?
Data is loaded from Firebase (and most modern cloud APIs) asynchronously, and while the data is being loaded the rest of your code continues to run. This is easiest to see if you add some logging to your code:
console.log("Before starting to load data")
for (let i = 0; i < categories.length; i++) {
admin.database().ref(categories[i]).once('value', (data) => {
console.log("Got data")
});
}
console.log("After starting to load data")
When you run this code, the output is:
Before starting to load data
After starting to load data
Got data
Got data
...
This is probably the order that you expected the output to be in, but it explains why your console.log(eventsData) shows an empty array: by the time you log the array, none of the data has been loaded yet and eventsData.push(data.val()) hasn't run.
The solution for this is always the same: any code that needs the data from the asynchronous call, needs to either be directly inside the callback, be called from there, or be otherwise synchronized.
Since you're loading multiple nodes we'll use Promise.all here to wait for all of those nodes to have been loaded.
exports.getRecipes = functions.region('europe-west1').https.onCall((data, context) => {
const categories = data.categories;
const eventsData = Promise.all(categories.map((category) => {
return admin.database().ref(categories[i]).once('value').then((snapshot) => {
return snapshot.val();
});
});
return eventsData;
});
Since we now return a promise, Cloud Functions will wait for that promise to resolve, and then return the resulting value to the caller.
I recommend learning more about promises and asynchronous behavior at:
The Firebase documentation on terminating functions: Sync, async, and promises.
Doug's video series on Learn JavaScript Promises (Pt.1) with HTTP Triggers in Cloud Functions
The MDN pages on Asynchronous JavaScript
I have been developing a game where on the user submitting data, the client writes some data to a Firebase Realtime Database. I then have a Google Cloud Function which is triggered onUpdate. That function checks the submissions from various players in a particular game and if certain criteria are met, the function writes an update to the DB which causes the clients to move to the next round of the game.
This is all working, however, I have found performance to be quite poor.
I've added logging to the function and can see that the function takes anywhere from 2-10ms to complete, which is acceptable. The issue is that the update is often written anywhere from 10-30 SECONDS after the function has returned the update.
To determine this, my function obtains the UTC epoch timestamp from just before writing the update, and stores this as a key with the value being the Firebase server timestamp.
I then have manually checked the two timestamps to arrive at the time between return and database write:
The strange thing is that I have another cloud function which is triggered by an HTTP request, and found that the update from that function is typically from 0.5-2 seconds after the function calls the DB update() API.
The difference between these two functions, aside from how they are triggered, is how the data is written back to the DB.
The onUpdate() function writes data by returning:
return after.ref.update(updateToWrite);
Whereas the HTTP request function writes data by calling the update API:
dbRef.update({
// object to write
});
I've provided a slightly stripped out version of my onUpdate function here (same structure but sanitised function names) :
exports.onGameUpdate = functions.database.ref("/games/{gameId}")
.onUpdate(async(snapshot, context) => {
console.time("onGameUpdate");
let id = context.params.gameId;
let after = snapshot.after;
let updatedSnapshot = snapshot.after.val();
if (updatedSnapshot.serverShouldProcess && !isFinished) {
processUpdate().then((res)=>{
// some logic to check res, and if criteria met, move on to promises to determine update data
determineDataToWrite().then((updateToWrite)=>{
// get current time
var d = new Date();
let triggerTime = d.getTime();
// I use triggerTime as the key, and firebase.database.ServerValue.TIMESTAMP as the value
if(updateToWrite["timestamps"] !== null && updateToWrite["timestamps"] !== undefined){
let timestampsCopy = updateToWrite["timestamps"];
timestampsCopy[triggerTime] = firebase.database.ServerValue.TIMESTAMP;
updateToWrite["timestamps"][triggerTime] = firebase.database.ServerValue.TIMESTAMP;
}else{
let timestampsObj = {};
timestampsObj[triggerTime] = firebase.database.ServerValue.TIMESTAMP;
updateToWrite["timestamps"] = timestampsObj;
}
// write the update to the database
return after.ref.update(updateToWrite);
}).catch((error)=>{
// error handling
})
}
// this is just here because ES Linter complains if there's no return
return null;
})
.catch((error) => {
// error handling
})
}
});
I'd appreciate any help! Thanks :)
I am currently evaluating WebViewer version 5.2.8.
I need to set some javascript function/code as an action for triggers like calculate trigger, format trigger and keystroke trigger through the WebViewer UI.
Please help me on how to configure javascript code for a form field trigger in WebViewer UI.
Thanks in advance,
Syed
Sorry for the late response!
You will have to create the UI components yourself that will take in the JavaScript code. You can do something similar to what the FormBuilder demo does with just HTML and JavaScript. However, it may be better to clone the open source UI and add your own components.
As for setting the action, I would recommend trying out version 6.0 instead as there is better support for widgets and form fields in that version. However, we are investigating a bug with the field actions that will throw an error on downloading the document. You should be able to use this code to get it working first:
docViewer.on('annotationsLoaded', () => {
const annotations = annotManager.getAnnotationsList();
annotations.forEach(annot => {
const action = new instance.Actions.JavaScript({ javascript: 'alert("Hello World!")' });
// C cor Calculate, and F for Format
annot.addAction('K', action);
});
});
Once the bug has been dealt with, you should be able to download the document properly.
Otherwise, you will have to use the full API and that may be less than ideal. It would be a bit more complicated with the full API and I would not recommend it if the above feature will be fixed soon.
Let me know if this helps or if you need more information about using the full API to accomplish this!
EDIT
Here is the code to do it with the full API! Since the full API works at a low level and very closely to the PDF specification, it does take a lot more to make it work. You do still have to update the annotations with the code I provided before which I will include again.
docViewer.on('documentLoaded', async () => {
// This part requires the full API: https://www.pdftron.com/documentation/web/guides/full-api/setup/
const doc = docViewer.getDocument();
// Get document from worker
const pdfDoc = await doc.getPDFDoc();
const pageItr = await pdfDoc.getPageIterator();
while (await pageItr.hasNext()) {
const page = await pageItr.current();
// Note: this is a PDF array, not a JS array
const annots = await page.getAnnots();
const numAnnots = await page.getNumAnnots();
for (let i = 0; i < numAnnots; i++) {
const annot = await annots.getAt(i);
const subtypeDict = await annot.findObj('Subtype');
const subtype = await subtypeDict.getName();
const actions = await annot.findObj('AA');
// Check to make sure the annot is of type Widget
if (subtype === 'Widget') {
// Create the additional actions dictionary if it does not exist
if (!actions) {
actions = await annot.putDict('AA');
}
let calculate = await actions.findObj('C');
// Create the calculate action (C) if it does not exist
if (!calculate) {
calculate = await actions.putDict('C');
await Promise.all([calculate.putName('S', 'JavaScript'), calculate.putString('JS', 'app.alert("Hello World!")')]);
}
// Repeat for keystroke (K) and format (F)
}
}
pageItr.next();
}
});
docViewer.on('annotationsLoaded', () => {
const annotations = annotManager.getAnnotationsList();
annotations.forEach(annot => {
const action = new instance.Actions.JavaScript({ javascript: 'app.alert("Hello World!")' });
// K for Keystroke, and F for Format
annot.addAction('C', action);
});
});
You can probably put them together under the documentLoaded event but once the fix is ready, you can delete the part using the full API.
I have tried an increment counter example (https://github.com/firebase/functions-samples/tree/master/child-count) with Cloud Functions which is referencing Realtime Database, but not Firebase Firestore.
I am following firestore document and tried some changes. Still facing issue and not able to run this code for firestore new document. I am writing cloud function first time, and I'm not sure I've written this right.
exports.countlikechange = functions.database.ref('/posts/{postid}/likes/{likeid}').onWrite(
async (change) => {
const collectionRef = change.after.ref.parent;
const countRef = collectionRef.parent.child('likes_count');
let increment;
if (change.after.exists() && !change.before.exists()) {
increment = 1;
} else if (!change.after.exists() && change.before.exists()) {
increment = -1;
} else {
return null;
}
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
await countRef.transaction((current) => {
return (current || 0) + increment;
});
console.log('Counter updated.');
return null;
});
I want to update count in parent document.
You should review the documentation for Firestore triggers. What you're writing is a Realtime Database trigger, because the function declaration is functions.database. You'll want to use functions.firestore instead. You're also using Realtime Database APIs instead of Firestore APIs. Your final solution that uses Firestore will look almost completely different than what you have now.
I wrote this node js function for google cloud function to index firebase database node entry to Algolia.
exports.indexlisting_algolia =
functions.database.ref('/Listings/{listingId}')
.onWrite((change, context) => {
const index = algolia.initIndex('Listings');
const before = change.before; // snapshot before the update
const after = change.after; // snapshot after the update
const before_data = before.val();
const after_data = after.val();
after_data.objectID = context.params.listingId;
console.log(Date.now());
console.log(context)
return index.saveObject(after_data)
.then(
() => change.after.ref.child('last_index_timestamp').set(
Date.parse(context.timestamp)));
})
the function works but it would not stop executing, it just keep repeating itself over and over again. What is wrong and how can I fix this?
By doing change.after.ref.child('last_index_timestamp').set() you are writing to the reference you are listening to in your Cloud Function. So the Cloud Function is auto-triggering itself.
You should check at the beginning of the function if it needs to be executed or not.
Most probably you would check if last_index_timestamp exists or has a specific value, by using change.before.val() and/or change.after.val().
If you want to stop the execution of the Function just return null.
See the following official sample for an example of this "technique" (lines 30 to 32) https://github.com/firebase/functions-samples/blob/952fe5d08c0a416f78def93fa337ca2bd73aedcf/message-translation/functions/index.js
A last (important) remark: you shall return the promise returned by the set() method as well as catch the potential errors, as follows:
return index.saveObject(after_data)
.then(
() => return change.after.ref.child('last_index_timestamp').set(Date.parse(context.timestamp))
)
.catch(err => {
console.log('Error:', err);
return false;
});
I would suggest that you watch the following official Video Series "Learning Cloud Functions for Firebase" (see https://firebase.google.com/docs/functions/video-series/), and in particular the three videos titled "Learn JavaScript Promises", which explain how and why we should chain and return promises in event triggered Cloud Functions.