I'm using firestore in my android project, everything working fine.But Yesterday an issue occurred on getting timestamp value from document snapshot.
if (documentChange.getType() == DocumentChange.Type.ADDED) {
Map<String, Object> stringObjectMap = documentChange.getDocument().getData();
Date date = (Date) stringObjectMap.get("timestamp"); }
This is one of my Collection Document.
id : "25",
message : "This is my message",
timestamp : December 11, 2017 at 10:39:12 PM UTC+5, \\This is Firebase FieldValue.serverTimestamp()
username : "Temp"
I get everything from the document except for timestamp.
If you are seeing this ADDED event with a null timestamp after writing from the same client, this is the expected behavior.
When you write with server timestamps you will get two events:
An initial event confirming the document write in the local cache.
At this point the timestamp is null because the server has not set
it yet.
A second event where the timestamp has been set by the server and
the client has observed the change.
We are working on an API to make this behavior configurable, but right now you need to handle this case.
Edit You can now control this behavior.
DocumentSnapshot snap = ...
// One of ESTIMATE, NONE, or PREVIOUS
DocumentSnapshot.ServerTimestampBehavior behavior = ...
Date date = snap.getDate("fieldName", behavior);
The previous behavior (returning null when not known) is equivalent to NONE in the new API. For more information, see the JavaDoc:
https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/DocumentSnapshot.ServerTimestampBehavior
Related
I have an app that uses the Firebase SDK to directly talk to Cloud Firestore from within the application. My code makes sure to only write data at reasonable intervals. But a malicious user might take the configuration data from my app, and use it to write an endless stream of data to my database.
How can I make sure a user can only write say once every few seconds, without having to write any server-side code.
Every read or write operation to your database, is validated on Google's servers by the security rules that you configured for your project. These rules can only be set by collaborators on your project, but apply to all client-side code that accesses the database in your project. This means that you can enforce this condition in these security rules, not even the malicious user can bypass them, since they don't have access to your project.
Say we have a users collection, and that each document in there has an ID with the UID of the user. These security rules make sure that the user can only write their own document, and no more than once every 5 seconds:
match /users/{document=**} {
allow create: if isMine() && hasTimestamp();
allow update: if isMine() && hasTimestamp() && isCalm();
function isMine() {
return request.resource.id == request.auth.uid;
}
function hasTimestamp() {
return request.resource.data.timestamp == request.time;
}
function isCalm() {
return request.time > resource.data.timestamp + duration.value(5, 's');
}
}
A walkthrough might help:
The first line determines the scope of the rules within them, so these rules apply to all documents within the /users collection.
A user can create a document if it's theirs (isMine()), if it has a timestamp (hasTimestamp()).
A user can update a document, if it's theirs, has a timestamp, and and if they don't write too often (isCalm()).
Let's look at all three functions in turn...
The isMine() function checks if the document ID is the same as the user who is performing the write operation. Since auth.uid is populated by Firebase automatically based on the user who is signed in, there is no way for a malicious user to spoof this value.
The hasTimestamp() function checks if the document that is being written (request.resource) has a timestamp field, and if so, if that timestamp is the same as the current server-side time. This means that in code, you will need to specify FieldValue.serverTimestamp() in order for the write to be acceptable. So you can only write the current server-side timestamp, and a malicious user can't pass in a different timestamp.
The isCalm() functions makes sure the user doesn't write too often. It allows the write if the difference between the timestamp values in the existing document (resource.data.timestamp) and the document (request.resource.data.timestamp) that is currently being written, is at least 5 seconds.
Per Doug's comment:
It's important to note that the above implements a per-document write limit, and not a per-account limit. The user is still free to write other documents as fast as the system allows.
Continue reading if you want to have a per-user write rate-limit, on all documents they write.
Here's a jsbin of how I tested these rules: https://jsbin.com/kejobej/2/edit?js,console. With this code:
firebase.auth().signInAnonymously().then(function(auth) {
var doc = collection.doc(auth.user.uid);
doc.set({
timestamp: firebase.firestore.FieldValue.serverTimestamp()
}).then(function() {
console.log("Written at "+new Date());
}).catch(function(error) {
console.error(error.code);
})
})
If you repeatedly click the Run button, it will only allow a next write if at least 5 seconds have passed since the previous one.
When I click the Run button about once a second, I got:
"Written at Thu Jun 06 2019 20:20:19 GMT-0700 (Pacific Daylight Time)"
"permission-denied"
"permission-denied"
"permission-denied"
"permission-denied"
"Written at Thu Jun 06 2019 20:20:24 GMT-0700 (Pacific Daylight Time)"
"permission-denied"
"permission-denied"
"permission-denied"
"permission-denied"
"Written at Thu Jun 06 2019 20:20:30 GMT-0700 (Pacific Daylight Time)"
The final example is a per-user write rate-limit. Say you have a social media application, where users create posts, and each user has a profile. So we have two collections: posts and users. And we want to ensure that a user can create a new post at most once every 5 seconds.
The rules for this are pretty much the same as before, as in: a user can update their own profile, and can create a post if they haven't written one in the past 5 seconds.
The big different is that we store the timestamp in their user profile (/users/$uid), even when they're creating a new post document (/posts/$newid). Since both of these writes need to happen as one, we'll use a BatchedWrite this time around:
var root = firebase.firestore();
var users = root.collection("users");
var posts = root.collection("posts");
firebase.auth().signInAnonymously().then(function(auth) {
var batch = db.batch();
var userDoc = users.doc(auth.user.uid);
batch.set(userDoc, {
timestamp: firebase.firestore.FieldValue.serverTimestamp()
})
batch.set(posts.doc(), {
title: "Hello world"
});
batch.commit().then(function() {
console.log("Written at "+new Date());
}).catch(function(error) {
console.error(error.code);
})
})
So the batch writes two things:
It writes the current server-side time to the user's profile.
It creates a new post with a title field.
The top-level security rules for this are (as said) pretty much the same as before:
match /users/{user} {
allow write: if isMine() && hasTimestamp();
}
match /posts/{post} {
allow write: if isCalm();
}
So a user can write to a profile doc if it's their own, and if that doc contains a timestamp that is equal to the current server-side/request time. A user can write a post, if they haven't posted too recently.
The implementation of isMine() and hasTimstamp() is the same as before. But the implementation of isCalm() now looks up the user profile document both before and after the write operation to do its timestamp check:
function isCalm() {
return getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp
> get(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's');
}
The path to get() and getAfter() unfortunately has to be absolute and fully qualified, but it boils down to this:
// These won't work, but are easier to read.
function isCalm() {
return getAfter(/users/$(request.auth.uid)).data.timestamp
> get(/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's');
}
A few things to note:
Just like before we're comparing two timestamps. But here we're reading the timestamps from different documents.
This requires reading two extra documents, which means you'll be charged for two extra read operations. If the purpose of the rate limit is to not be charged for the write operations of a malicious user, this may not be the solution you're looking for.
I want to get the date from the Firestore server for consistency, and not from the device. Timestamp(Date()).toDate() gives time as July 6, 2021 at 9:23:34 PM UTC+0 (timestamp). But I need only the date part, such as '11-11-2021'. Is there any way to save only the date part with Firebase server timestamp?
Code:
val docData = hashMapOf(
"stringExample" to "Hello world!",
"dateExample" to Timestamp(Date()).toDate(), //want only date part. Also is there alternative of using Date()?
"nullExample" to null
)
db.collection("data").document("one")
.set(docData)
.addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
.addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }
Want to do this because it's better not to strip data on some usage such as for small iot device.
The most appropriate way for saving the Date and Time would be to set a Timestamp field, as explained in my answer from the following code:
How to add a Timestamp in Firestore with Android?
If you try to save the dates as String values '11-11-2021', then you'll not be able to order the results, because when you order String elements, the order is lexicographical. Besides that, in terms of storage, the Date field will occupy less space than '11-11-2021'. According to the official documentation regarding storage field size calculation:
The size of Date field values is 8 bytes, while the value of String text is the number of UTF-8 encoded bytes + 1.
So we have 8 bytes vs. 11 bytes. So the best option that you have, is to store the Date as a Firestore Timestamp using FieldValue.serverTimestamp().
There's no method on the firebase.firestore.Timestamp class that will achieve what you're looking for. You could format the dates once they've been retrieved however, and if you're looking for a quick date formatting for display then perhaps .toDateString() could be useful. Otherwise you could create your own property on the firestore documents and manually assign just the date when adding docs.
This is in reference to this question Firebase Timestamp difference between current Timestamp and database timestamp error
If I use Timestamp.now() there are cases where the device clock is out of sync with Firestore and my time calculations are not accurate. I want to get the exact time from the Firestore server without being bothered by out of sync cases. Is there any way I could do that?
I have seen FieldValue.serverTimestamp() but there I cannot find a way to calculate anything using it, since it's return type is FieldValue and I am unable to parse it to Timestamp.
If you are using FieldValue.serverTimestamp() it means you let Firestore generate a timestamp for you. Please note that this timestamp is generated entirely on the server. It is not generated as document ids are, for instance on the client.
I cannot find a way to calculate anything using it since it's return type is FieldValue and I am unable to parse it to Timestamp.
When calling FieldValue's serverTimestamp() method, it true that type of the object that is returned is FieldValue but this only used to be passed as an argument when you need that timestamp property. Please check how you can add a timestamp to a Cloud Firestore document:
ServerTimestamp is always null on Firebase Firestore
So if you query the database and you get a document that has a property that was set using FieldValue.serverTimestamp(), the type of object that is returned is Date. Since you get a Date object back, you can do whatever you want with it.
However, in Firebase Realtime Database we have another mechanism. So please see this answer:
How to save the current date/time when I add new value to Firebase Realtime Database
So, in this case, it's totally different since we are using the time since the Unix epoch, in milliseconds and not a Date object.
Edit:
It sounds like your question started off about how to get a Timestamp from Firestore, but is actually just about how to structure a document so two devices can read the same document. However, to make it work, you should use two properties of type Date. One for device A and one for device B. Check the schema below:
Firestore-root
|
--- yourCollection (collection)
|
--- yourDocument (document)
|
--- deviceA: January 24, 2020 at 3:37:59 PM UTC+3
|
--- deviceB: January 25, 2020 at 3:37:59 PM UTC+3 //Timestamp
To get the values of deviceA and deviceB properties, please use my answer from the following post:
How to get server Timestamp from Firestore in an android device?
Now you can compare both timestamps.
One obvious but economically adverse way is to write to Firestore Database, the timestamp and read it again from there.
db.collection("TimestampChecker").document("some_string").set(new TimestampGetter()).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
db.collection("TimestampChecker").document("some_string").get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if(task.getResult()!=null)
{
TimestampGetter timestampGetter = task.getResult().toObject(TimestampGetter.class);
Date dateNow = timestampGetter.getTimestampNow();
}
}
});
}
});
And TimestampGetter can be a class like this:
public class TimestampGetter {
#ServerTimestamp
private Date timestampNow;
public TimestampGetter(Date timestampNow) {
this.timestampNow = timestampNow;
}
public TimestampGetter() {
}
public Date getTimestampNow() {
return timestampNow;
}
public void setTimestampNow(Date timestampNow) {
this.timestampNow = timestampNow;
}}
I keep a local copy of a simple database in user's phone. This way the app can be used offline. I want to check with firebase server from time to time and update the local database if there is any change. So I need to know last action (insert, update, delete, etc.) time in a specified location in Firebase database. Is that possible? Or should I implement my own mechanism?
The Firebase Database does not store informations like the timestamp for CRUD operations that are performed. Because of that, you need to store this kind of data yourself with your own mechanism. So, you need to create a new field for each child you want to trace and change the value of the TIMESTAMP every time a action is performed. The best practice is to save your data as a TIMESTAMP like this: ServerValue.TIMESTAMP. Note, that when you are saving the TIMESTAMP, you are saving as a Map and when you are retrieving, you are retrieving it as a long. To set the TIMESTAMP, i recomand you using the following code:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
Map map = new HashMap();
map.put("time", ServerValue.TIMESTAMP);
ref.child("yourNode").updateChildren(map);
To get you data back, i recomand you using the following method:
public static String getTimeDate(long timeStamp){
try{
DateFormat dateFormat = getDateTimeInstance();
Date netDate = (new Date(timeStamp));
return dateFormat.format(netDate);
} catch(Exception e) {
return "date";
}
}
This is possible but you shouldn't
Firebase already has offline capabilities, is very simple to use. Is a 2 steps process:
Set offline capabilities on
Set what you want to keep track ass offline
This is the official documentation
In code this is done something like this:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);
There is big temptation to use:
FirebaseDatabase.getInstance().getReference().keepSynced(true);
Trying to keep everything sync by syncing the root reference doesn't work, you have to be specific, at least at 1 level parent.
The syncing when the connection is resumed is automatic, the user has to open the app though, flawless.
Now, if you still insist on doing this, then what you have to do is set ServeValue.TimeStamp
In the same link provided above, you can find how to set the server timestamp, here is the direct reference.
Later you can sort by that timestamp, here is a more detailed answer
I have a Mongoose schema with the timestamp option set as true.
schema = new mongoose.Schema({
...
},
{ timestamps: true });
Now I have an Android application that gets the timestamp using System.currentTimeMillis() which works all well and good and gives me number of milliseconds since UNIX epoch time.
I send this data across to my Node.js / Express server which takes the time and returns only documents created after that particular date.
// get all docs
router.get('/api/seekers', function(req, res) {
Seeker.find({createdAt:{ $gt: new Date(req.query.timestamp) }}, function(err, seekers) {
if(err)
res.send(err);
else
res.json(seekers);
});
});
So I send https://api_url.com/api/seekers?timestamp=1479431351762 as a request to the server.
Now a couple of things happen:
I send the value in milliseconds and get this error
{"message":"Cast to date failed for value \"Invalid Date\" at path \"updatedAt\"","name":"CastError","kind":"date","value":null,"path":"updatedAt"}
After a little bit of investigation, it turns out you need to pass seconds to Date(). So,
I divide the value by 1000 to get seconds (req.query.timestamp/1000). Now I wasn't getting an error, but the query constraint was not working. I'm getting all values since the beginning of time.
I moved over to the Mongo shell to check whether the issue persists, to which it turns out it doesn't since I can pass the millisecond value to Mongo's Date:
> new Date(1479431351762)
ISODate("2016-11-18T01:09:11.762Z")
But if I tried passing the second value to Date(), it turns out it actually was sending me to the beginning of time:
> new Date(1479431351)
ISODate("1970-01-18T02:57:11.351Z")
I'm unable to figure this out, what can I do so server request and mongoose correctly processes the timestamp and queries my db?
Any wanderers with the same issue, who might have missed chridam's comment, you just need to cast the passed timestamp to int before parsing. This works:
new Date(parseInt(req.query.timestamp))