Firebase Cloud function event - sometime not getting data on update event - javascript

I have written firebase cloud function to trigger on update record. sometimes I am not getting the same record which is updating. I am adding my code below.Please check attached image also.
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}/userResponse').onUpdate(event => {
return admin.database().ref(`/Notification/${event.params.userId}/${event.params.notificationId}`).once('value').then(function (snapshot) {
var notification = snapshot.val();
if (!notification) {
console.error("Notification not found on notification update");
return;
};
I can also get Notification object from the parent but I want to know issue best approach and the problem with this code.
this is error log
this is database structure
This is my 1st post here please let me know if need more information.
Thanks

You don't have to call once within the Function since it is already returning the data at the location you are listening to, just listen to the parent node.
So you should do like:
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}').onUpdate(event => {
const notification = event.data.val();
if (notification === null) {
console.error("Notification not found on notification update");
return null;
//actually this would only be called in case of deletion of the Notification
} else {
//do something with the notification data: send Android notification, send mail, write in another node of the database, etc.
//BUT return a Promise
//notification const declared above is a JavaScript object containing what is under this node (i.e. a similar structure than your database structure as shown in the image within your post.)
}
});
I would suggest that you have a look at these three videos from the Firebase team:
https://www.youtube.com/watch?v=7IkUgCLr5oA&t=517s
https://www.youtube.com/watch?v=652XeeKNHSk&t=27s
https://www.youtube.com/watch?v=d9GrysWH1Lc
Also, note that Cloud Functions have been updated and the first line of your code shall be written differently if you are using a CF version above 1.0.0. See https://firebase.google.com/docs/functions/beta-v1-diff

Related

listen to the single field for realtime update in firestore [duplicate]

How can I listen to a specific field change with firestore js sdk ?
In the documentation, they only seem to show how to listen for the whole document, if any of the "SF" field changes, it will trigger the callback.
db.collection("cities").doc("SF")
.onSnapshot(function(doc) {
console.log("Current data: ", doc && doc.data());
});
You can't. All operations in Firestore are on an entire document.
This is also true for Cloud Functions Firestore triggers (you can only receive an entire document that's changed in some way).
If you need to narrow the scope of some data to retrieve from a document, place that in a document within a subcollection, and query for that document individually.
As Doug mentioned above, the entire document will be received in your function. However, I have created a filter function, which I named field, just to ignore document changes when those happened in fields that I am not interested in.
You can copy and use the function field linked above in your code. Example:
export const yourCloudFunction = functions.firestore
.document('/your-path')
.onUpdate(
field('foo', 'REMOVED', (change, context) => {
console.log('Will get here only if foo was removed');
}),
);
Important: The field function is not avoiding your function to be executed if changes happened in other fields, it will just ignore when the change is not what you want. If your document is too big, you should probably consider Doug's suggestion.
Listen for the document, then set a conditional on the field you're interesting in:
firebase.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(word).collection('Pronunciations').doc('Castilian-female-IBM').onSnapshot(function(snapshot) {
if (snapshot.data().audioFiles) { // eliminates an error message
if (snapshot.data().audioFiles.length === 2) {
audioFilesReady++;
if (audioFilesReady === 3) {
$scope.showNextWord();
}
}
}
}, function(error) {
console.error(error);
});
I'm listening for a document for a voice (Castilian-female-IBM), which contains an array of audio files, in webm and mp3 formats. When both of those audio files have come back asynchronously then snapshot.data().audioFiles.length === 2. This increments a conditional. When two more voices come back (Castilian-male-IBM and Latin_American-female-IBM) then audioFilesReady === 3 and the next function $scope.showNextWord() fires.
Just out of the box what I do is watching before and after with the before and after method
const clientDataBefore = change.before.data();
console.log("Info database before ", clientDataBefore);
const clientDataAfter = change.after.data();
console.log("Info database after ", clientDataAfter );
For example now you should compare the changes for a specific field and do some actions or just return it.
Some more about before.data() and after.data() here

Filter collection by lastModified

I need to fetch sub-set of documents in Firestore collection modified after some moment. I tried going theses ways:
It seems that native filtering can work only with some real fields in stored document - i.e. nevertheless Firestore API internally has DocumentSnapshot.getUpdateTime() I cannot use this information in my query.
I tried adding my _lastModifiedAt 'service field' via server-side firestore cloud function, but ... that updating of _lastModifiedAt causes recursive invocation of the onWrite() function. I.e. is does also not work as needed (recursion finally stops with Error: quota exceeded (Function invocations : per 100 seconds)).
Are there other ideas how to filter collection by 'lastModifiedTime'?
Here is my 'cloud function' for reference
It would work if I could identify who is modifying the document, i.e. ignore own updates of _lastModified field, but I see no way to check for this
_lastModifiedBy is set to null because of current inability of Firestore to provide auth information (see here)
exports.updateLastModifiedBy = functions.firestore.document('/{collId}/{documentId}').onWrite(event => {
console.log(event.data.data());
var lastModified = {
_lastModifiedBy: null,
_lastModifiedAt: now
}
return event.data.ref.set(lastModified, {merge: true});
});
I've found the way to prevent recursion while updating '_lastModifiedAt'.
Note: this will not work reliably if client can also update '_lastModifiedAt'. It does not matter much in my environment, but in general case I think writing to '_lastModifiedAt' should be allowed only to service accounts.
exports.updateLastModifiedBy = functions.firestore.document('/{collId}/{documentId}').onWrite(event => {
var doc = event.data.data();
var prevDoc = event.data.previous.data();
if( doc && prevDoc && (doc._lastModifiedAt != prevDoc._lastModifiedAt) )
// this is my own change
return 0;
var lastModified = getLastModified(event);
return event.data.ref.set(lastModified, {merge: true});
});
Update: Warning - updating lastModified in onWrite() event causes infinite recursion when trying to delete all documents in Firebase console. This happens because onWrite() is also triggered for delete and writing lastModified into deleted document actually resurrects it. That document propagates back into console and is tried to be deleted once again, indefinitely (until WEB page is closed).
To fix that issue above mentioned code has to be specified individually for onCreate() and onUpdate().
How about letting the client write the timestamp with FieldValue.serverTimestamp() and then validate that the value written is equal to time in security rules?
Also see Mike's answer here for an example: Firestore Security Rules: If timestamp (FieldValue.serverTimestamp) equals now
You could try the following function, which will not update the _lastModifiedAt if it has been marked as modified within the last 5 seconds. This should ensure that this function only runs once, per update (as long as you don't update more than once in 5 seconds).
exports.updateLastModifiedBy = functions.firestore.document('/{collId}/{documentId}').onWrite(event => {
console.log(event.data.data());
if ((Date.now() - 5000) < event.data.data()._lastModifiedAt) {return null};
var lastModified = {
_lastModifiedBy: null,
_lastModifiedAt: now
}
return event.data.ref.set(lastModified, {merge: true});
});

Errors with IndexedDB versions and Dexie.js

I´m starting with IndexedDB and to not reinvent the wheel I´m using Dexie.js https://github.com/dfahlander/Dexie.js
I created the database, I added data and now I´m creating a generic function that get a CSV and populate the database in anothers tables.
So, more or less my code is
// Creation and populate database and first table
var db = new Dexie("database");
db.version(1).stores({table1: '++id, name'});
db.table1.add({name: 'hello'});
Until here all is OK
Now, in success of ajax request
db.close();
db.version(2).stores({table2: '++id, name'});
db.open();
db.table2.add({name: 'hello'});
First time this code run everything is OK, but next time I get this error
VersionError The operation failed because the stored database is a
higher version than the version requested.
If I delete database and run code again only first time works OK.
Any idea? I don´t like too much IndexedDB version way, it´s looks frustrating and I don't get lot of help in the Net
Thanks.
Edit:
I discover the ¿problem/bug/procedure?. If I don´t add nothing before any version modification I haven't this issue, but does somebody know if is this the normal procedure?
So.. if this is the procedure I can't add any table dinamycally with a generic method. First all declarations and then add values. Any possibility to add a table after add values?
Edit again... I just realized that I could create another database. I'll post results. But any information about this issue is welcome :)
Edit again... I created dinamycally another database and everybody is happy!!
That is because the second time the code runs, your database is on version 2, but your main code still tries to open it at version 1.
If not knowing the current version installed, try opening dexie in dynamic mode. This is done by not specifying any version:
var db = new Dexie('database');
db.open().then(function (db) {
console.log("Database is at version: " + db.verno);
db.tables.forEach(function (table) {
console.log("Found a table with name: " + table.name);
});
});
And to dynamically add a new table:
function addTable (tableName, tableSchema) {
var currentVersion = db.verno;
db.close();
var newSchema = {};
newSchema[tableName] = tableSchema;
// Now use statically opening to add table:
var upgraderDB = new Dexie('database');
upgraderDB.version(currentVersion + 1).stores(newSchema);
return upgraderDB.open().then(function() {
upgraderDB.close();
return db.open(); // Open the dynamic Dexie again.
});
}
The latter function returns a promise to wait until it's done before using the new table.
If your app resides in several browsers, the other windows will get their db connection closed as well so they can never trust the db instance to be open at any time. You might want to listen for db.on('versionchange') (https://github.com/dfahlander/Dexie.js/wiki/Dexie.on.versionchange) to override the default behavior for that:
db.on("versionchange", function() {
db.close(); // Allow other page to upgrade schema.
db.open() // Reopen the db again.
.then(()=> {
// New table can be accessed from now on.
}).catch(err => {
// Failed to open. Log or show!
});
return false; // Tell Dexie's default implementation not to run.
};

Firebase array item is removed and immediately auto-added back (with AngularFire)

I am trying to remove an item from $firebaseArray (boxes).
The remove funcion:
function remove(boxJson) {
return boxes.$remove(boxJson);
}
It works, however it is immediately added back:
This is the method that brings the array:
function getBoxes(screenIndex) {
var boxesRef = screens
.child("s-" + screenIndex)
.child("boxes");
return $firebaseArray(boxesRef);
}
I thought perhaps I'm holding multiple references to the firebaseArray and when one deletes, the other adds, but then I thought firebase should handle it, no?
Anyway I'm lost on this, any idea?
UPDATE
When I hack it and delete twice (with a timeout) it seems to work:
function removeForce(screenIndex, boxId) {
setTimeout(function () {
API.removeBox(screenIndex, boxId);
}, 1000);
return API.removeBox(screenIndex, boxId);
}
and the API.removeBox:
function removeBox(screenIndex, boxId) {
var boxRef = screens
.child("s-" + screenIndex)
.child("boxes")
.child(boxId);
return boxRef.remove();
}
When you remove something from firebase it is asynchronous. Per the docs the proper way to remove an item is from firebase, using AngularFire is:
var obj = $firebaseObject(ref);
obj.$remove().then(function(ref) {
// data has been deleted locally and in the database
}, function(error) {
console.log("Error:", error);
});
$remove() ... Removes the entire object locally and from the database. This method returns a promise that will be fulfilled when the data has been removed from the server. The promise will be resolved with a Firebase reference for the exterminated record.
Link to docs: https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject-remove
The most likely cause is that you have a security rules that disallows the deletion.
When you call boxes.$remove Firebase immediately fires the child_removed event locally, to ensure the UI is updated quickly. It then sends the command to the Firebase servers to check it and update the database.
On the server there is a security rule that disallows this deletion. The servers send a "it failed" response back to the client, which then raises a child_added event to fix the UI.
Appearantly I was saving the items again after deleting them. Clearly my mistake:
function removeSelected(boxes) {
var selectedBoxes = Selector.getSelectedBoxes(boxes);
angular.forEach(selectedBoxes, function (box) {
BoxManager.remove(box);
});
Selector.clearSelection(boxes, true);
}
In the clearSelection method I was updating a field on the boxes and saved them again.
Besides the obvious mistake this is a lesson for me on how to work with Firebase. If some part of the system keeps a copy of your deleted item, saving it won't produce a bug but revive the deleted item.
For those, who have the similar issue, but didn't solve it yet.
There are two methods for listening events: .on() and .once(). In my case that was the cause of a problem.
I was working on a migration procedure, that should run once
writeRef
.orderByChild('text_hash')
.equalTo(addItem.text_hash)
.on('value', val => { // <--
if (!val.exists()) {
writeRef.push(addItem)
}
});
So the problem was exactly because of .on method. It fires each time after a data manipulation from FB's console.
Changing to .once solved that.

node.js/javascript/couchdb view to associative array does not seem to work

I am trying to create a webapp on a node/couchdb/windows stack but get terribly stung by what seems to be a lack of experience.
In the database, there is a view that returns all users with passwords. Based on the tutorial for a blog I have tried to access the view through my node code.
Whenever I investigate the structure of the users or users variable, I get an undefined object.
The call to getDatabase() has been tested elsewhere and works at least for creating new documents.
function GetUser(login)
{
var users = GetUsers();
return users[login];
}
function GetUsers() {
var db = getDatabase();
var usersByEmail = [];
db.view("accounts", "password_by_email")
.then(function (resp) {
resp.rows.forEach(function (x) { usersByEmail[x.key] = x.value});
});
//usersByEmail['test'] = 'test';
return usersByEmail;
}
I am aware that both the use of non-hashed passwords as well as reading all users from the database is prohibitive in the final product - just in case anyone wanted to comment on that.
In case something is wrong with the way I access the view: I am using a design document called '_design/accounts' with the view name 'password_by_email'.
Your call to db.view is asynchronous, so when you hit return usersByEmail the object hasn't yet been populated. You simply can't return values from async code; you need to have it make a callback that will execute the code that relies on the result.

Categories