diff --git a/.changeset/firestore-server-timestamps.md b/.changeset/firestore-server-timestamps.md new file mode 100644 index 000000000..8f12b6406 --- /dev/null +++ b/.changeset/firestore-server-timestamps.md @@ -0,0 +1,5 @@ +--- +'@capacitor-firebase/firestore': minor +--- + +feat(firestore): support `serverTimestamps` behavior option for `addDocumentSnapshotListener(...)`, `addCollectionSnapshotListener(...)`, and `addCollectionGroupSnapshotListener(...)` diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestore.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestore.java index a16f8721c..eb8c780ac 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestore.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestore.java @@ -9,6 +9,7 @@ import com.google.firebase.firestore.AggregateSource; import com.google.firebase.firestore.CollectionReference; import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.Filter; import com.google.firebase.firestore.FirebaseFirestoreSettings; import com.google.firebase.firestore.ListenerRegistration; @@ -269,6 +270,9 @@ public void getCountFromServer(@NonNull GetCountFromServerOptions options, @NonN public void addDocumentSnapshotListener(@NonNull AddDocumentSnapshotListenerOptions options, @NonNull NonEmptyResultCallback callback) { String reference = options.getReference(); String callbackId = options.getCallbackId(); + DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior = FirebaseFirestoreHelper.createServerTimestampBehavior( + options.getServerTimestampBehavior() + ); ListenerRegistration listenerRegistration = getFirebaseFirestoreInstance() .document(reference) @@ -278,7 +282,7 @@ public void addDocumentSnapshotListener(@NonNull AddDocumentSnapshotListenerOpti if (exception != null) { callback.error(exception); } else { - GetDocumentResult result = new GetDocumentResult(documentSnapshot); + GetDocumentResult result = new GetDocumentResult(documentSnapshot, serverTimestampBehavior); callback.success(result); } } @@ -294,6 +298,9 @@ public void addCollectionSnapshotListener( QueryCompositeFilterConstraint compositeFilter = options.getCompositeFilter(); QueryNonFilterConstraint[] queryConstraints = options.getQueryConstraints(); String callbackId = options.getCallbackId(); + DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior = FirebaseFirestoreHelper.createServerTimestampBehavior( + options.getServerTimestampBehavior() + ); Query query = getFirebaseFirestoreInstance().collection(reference); if (compositeFilter != null) { @@ -312,7 +319,7 @@ public void addCollectionSnapshotListener( if (exception != null) { callback.error(exception); } else { - GetCollectionResult result = new GetCollectionResult(querySnapshot); + GetCollectionResult result = new GetCollectionResult(querySnapshot, serverTimestampBehavior); callback.success(result); } } @@ -328,6 +335,9 @@ public void addCollectionGroupSnapshotListener( QueryCompositeFilterConstraint compositeFilter = options.getCompositeFilter(); QueryNonFilterConstraint[] queryConstraints = options.getQueryConstraints(); String callbackId = options.getCallbackId(); + DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior = FirebaseFirestoreHelper.createServerTimestampBehavior( + options.getServerTimestampBehavior() + ); Query query = getFirebaseFirestoreInstance().collectionGroup(reference); if (compositeFilter != null) { @@ -346,7 +356,7 @@ public void addCollectionGroupSnapshotListener( if (exception != null) { callback.error(exception); } else { - GetCollectionResult result = new GetCollectionResult(querySnapshot); + GetCollectionResult result = new GetCollectionResult(querySnapshot, serverTimestampBehavior); callback.success(result); } } diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestoreHelper.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestoreHelper.java index e966d4599..09b1b22f7 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestoreHelper.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestoreHelper.java @@ -27,6 +27,21 @@ public class FirebaseFirestoreHelper { + @NonNull + public static DocumentSnapshot.ServerTimestampBehavior createServerTimestampBehavior(@Nullable String value) { + if (value == null) { + return DocumentSnapshot.ServerTimestampBehavior.NONE; + } + switch (value) { + case "estimate": + return DocumentSnapshot.ServerTimestampBehavior.ESTIMATE; + case "previous": + return DocumentSnapshot.ServerTimestampBehavior.PREVIOUS; + default: + return DocumentSnapshot.ServerTimestampBehavior.NONE; + } + } + public static HashMap createHashMapFromJSONObject(JSONObject object) throws JSONException { HashMap map = new HashMap<>(); Iterator keys = object.keys(); diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestorePlugin.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestorePlugin.java index f0dafbebf..87110ae87 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestorePlugin.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/FirebaseFirestorePlugin.java @@ -463,6 +463,7 @@ public void addDocumentSnapshotListener(PluginCall call) { return; } Boolean includeMetadataChanges = call.getBoolean("includeMetadataChanges"); + String serverTimestampBehavior = call.getString("serverTimestamps"); String callbackId = call.getCallbackId(); this.pluginCallMap.put(callbackId, call); @@ -470,6 +471,7 @@ public void addDocumentSnapshotListener(PluginCall call) { AddDocumentSnapshotListenerOptions options = new AddDocumentSnapshotListenerOptions( reference, includeMetadataChanges, + serverTimestampBehavior, callbackId ); NonEmptyResultCallback callback = new NonEmptyResultCallback() { @@ -505,6 +507,7 @@ public void addCollectionSnapshotListener(PluginCall call) { JSObject compositeFilter = call.getObject("compositeFilter"); JSArray queryConstraints = call.getArray("queryConstraints"); Boolean includeMetadataChanges = call.getBoolean("includeMetadataChanges"); + String serverTimestampBehavior = call.getString("serverTimestamps"); String callbackId = call.getCallbackId(); this.pluginCallMap.put(callbackId, call); @@ -514,6 +517,7 @@ public void addCollectionSnapshotListener(PluginCall call) { compositeFilter, queryConstraints, includeMetadataChanges, + serverTimestampBehavior, callbackId ); NonEmptyResultCallback callback = new NonEmptyResultCallback() { @@ -549,6 +553,7 @@ public void addCollectionGroupSnapshotListener(PluginCall call) { JSObject compositeFilter = call.getObject("compositeFilter"); JSArray queryConstraints = call.getArray("queryConstraints"); Boolean includeMetadataChanges = call.getBoolean("includeMetadataChanges"); + String serverTimestampBehavior = call.getString("serverTimestamps"); String callbackId = call.getCallbackId(); this.pluginCallMap.put(callbackId, call); @@ -558,6 +563,7 @@ public void addCollectionGroupSnapshotListener(PluginCall call) { compositeFilter, queryConstraints, includeMetadataChanges, + serverTimestampBehavior, callbackId ); NonEmptyResultCallback callback = new NonEmptyResultCallback() { diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionGroupSnapshotListenerOptions.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionGroupSnapshotListenerOptions.java index d446ad937..8f2f986a1 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionGroupSnapshotListenerOptions.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionGroupSnapshotListenerOptions.java @@ -24,17 +24,22 @@ public class AddCollectionGroupSnapshotListenerOptions { private boolean includeMetadataChanges; + @Nullable + private final String serverTimestampBehavior; + public AddCollectionGroupSnapshotListenerOptions( String reference, @Nullable JSObject compositeFilter, @Nullable JSArray queryConstraints, @Nullable Boolean includeMetadataChanges, + @Nullable String serverTimestampBehavior, String callbackId ) throws JSONException { this.reference = reference; this.compositeFilter = FirebaseFirestoreHelper.createQueryCompositeFilterConstraintFromJSObject(compositeFilter); this.queryConstraints = FirebaseFirestoreHelper.createQueryNonFilterConstraintArrayFromJSArray(queryConstraints); this.includeMetadataChanges = includeMetadataChanges == null ? false : includeMetadataChanges; + this.serverTimestampBehavior = serverTimestampBehavior; this.callbackId = callbackId; } @@ -56,6 +61,11 @@ public boolean isIncludeMetadataChanges() { return includeMetadataChanges; } + @Nullable + public String getServerTimestampBehavior() { + return serverTimestampBehavior; + } + public String getCallbackId() { return callbackId; } diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionSnapshotListenerOptions.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionSnapshotListenerOptions.java index 7d6adae9b..7eba13aac 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionSnapshotListenerOptions.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddCollectionSnapshotListenerOptions.java @@ -25,17 +25,22 @@ public class AddCollectionSnapshotListenerOptions { private final boolean includeMetadataChanges; + @Nullable + private final String serverTimestampBehavior; + public AddCollectionSnapshotListenerOptions( String reference, @Nullable JSObject compositeFilter, @Nullable JSArray queryConstraints, @Nullable Boolean includeMetadataChanges, + @Nullable String serverTimestampBehavior, String callbackId ) throws JSONException { this.reference = reference; this.compositeFilter = FirebaseFirestoreHelper.createQueryCompositeFilterConstraintFromJSObject(compositeFilter); this.queryConstraints = FirebaseFirestoreHelper.createQueryNonFilterConstraintArrayFromJSArray(queryConstraints); this.includeMetadataChanges = includeMetadataChanges == null ? false : includeMetadataChanges; + this.serverTimestampBehavior = serverTimestampBehavior; this.callbackId = callbackId; } @@ -57,6 +62,11 @@ public boolean isIncludeMetadataChanges() { return includeMetadataChanges; } + @Nullable + public String getServerTimestampBehavior() { + return serverTimestampBehavior; + } + public String getCallbackId() { return callbackId; } diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddDocumentSnapshotListenerOptions.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddDocumentSnapshotListenerOptions.java index ab0c2717a..34c4d86e7 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddDocumentSnapshotListenerOptions.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/options/AddDocumentSnapshotListenerOptions.java @@ -6,11 +6,21 @@ public class AddDocumentSnapshotListenerOptions { private String reference; private final boolean includeMetadataChanges; + + @Nullable + private final String serverTimestampBehavior; + private String callbackId; - public AddDocumentSnapshotListenerOptions(String reference, @Nullable Boolean includeMetadataChanges, String callbackId) { + public AddDocumentSnapshotListenerOptions( + String reference, + @Nullable Boolean includeMetadataChanges, + @Nullable String serverTimestampBehavior, + String callbackId + ) { this.reference = reference; this.includeMetadataChanges = includeMetadataChanges == null ? false : includeMetadataChanges; + this.serverTimestampBehavior = serverTimestampBehavior; this.callbackId = callbackId; } @@ -22,6 +32,11 @@ public boolean isIncludeMetadataChanges() { return includeMetadataChanges; } + @Nullable + public String getServerTimestampBehavior() { + return serverTimestampBehavior; + } + public String getCallbackId() { return callbackId; } diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetCollectionResult.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetCollectionResult.java index f5965aa9d..81e2e9a45 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetCollectionResult.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetCollectionResult.java @@ -1,7 +1,9 @@ package io.capawesome.capacitorjs.plugins.firebase.firestore.classes.results; +import androidx.annotation.NonNull; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; +import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.QueryDocumentSnapshot; import com.google.firebase.firestore.QuerySnapshot; import io.capawesome.capacitorjs.plugins.firebase.firestore.FirebaseFirestoreHelper; @@ -12,15 +14,23 @@ public class GetCollectionResult implements Result { private QuerySnapshot querySnapshot; + @NonNull + private DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior; + public GetCollectionResult(QuerySnapshot querySnapshot) { + this(querySnapshot, DocumentSnapshot.ServerTimestampBehavior.NONE); + } + + public GetCollectionResult(QuerySnapshot querySnapshot, @NonNull DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior) { this.querySnapshot = querySnapshot; + this.serverTimestampBehavior = serverTimestampBehavior; } @Override public JSObject toJSObject() { JSArray snapshotsResult = new JSArray(); for (QueryDocumentSnapshot document : querySnapshot) { - JSObject snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromMap(document.getData()); + JSObject snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromMap(document.getData(serverTimestampBehavior)); JSObject snapshotResult = new JSObject(); snapshotResult.put("id", document.getId()); diff --git a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetDocumentResult.java b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetDocumentResult.java index a75c5d2db..2a9dc0385 100644 --- a/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetDocumentResult.java +++ b/packages/firestore/android/src/main/java/io/capawesome/capacitorjs/plugins/firebase/firestore/classes/results/GetDocumentResult.java @@ -1,5 +1,6 @@ package io.capawesome.capacitorjs.plugins.firebase.firestore.classes.results; +import androidx.annotation.NonNull; import com.getcapacitor.JSObject; import com.google.firebase.firestore.DocumentSnapshot; import io.capawesome.capacitorjs.plugins.firebase.firestore.FirebaseFirestoreHelper; @@ -10,12 +11,20 @@ public class GetDocumentResult implements Result { private DocumentSnapshot documentSnapshot; + @NonNull + private DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior; + public GetDocumentResult(DocumentSnapshot documentSnapshot) { + this(documentSnapshot, DocumentSnapshot.ServerTimestampBehavior.NONE); + } + + public GetDocumentResult(DocumentSnapshot documentSnapshot, @NonNull DocumentSnapshot.ServerTimestampBehavior serverTimestampBehavior) { this.documentSnapshot = documentSnapshot; + this.serverTimestampBehavior = serverTimestampBehavior; } public JSObject toJSObject() { - Object snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromMap(documentSnapshot.getData()); + Object snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromMap(documentSnapshot.getData(serverTimestampBehavior)); JSObject snapshotResult = new JSObject(); snapshotResult.put("id", documentSnapshot.getId()); diff --git a/packages/firestore/ios/Plugin/Classes/Options/AddCollectionGroupSnapshotListenerOptions.swift b/packages/firestore/ios/Plugin/Classes/Options/AddCollectionGroupSnapshotListenerOptions.swift index d104793cf..c3a7d666b 100644 --- a/packages/firestore/ios/Plugin/Classes/Options/AddCollectionGroupSnapshotListenerOptions.swift +++ b/packages/firestore/ios/Plugin/Classes/Options/AddCollectionGroupSnapshotListenerOptions.swift @@ -6,13 +6,15 @@ import Capacitor private var compositeFilter: QueryCompositeFilterConstraint? private var queryConstraints: [QueryNonFilterConstraint] private var includeMetadataChanges: Bool + private var serverTimestampBehavior: String? private var callbackId: String - init(reference: String, compositeFilter: JSObject?, queryConstraints: [JSObject]?, includeMetadataChanges: Bool, callbackId: String) { + init(reference: String, compositeFilter: JSObject?, queryConstraints: [JSObject]?, includeMetadataChanges: Bool, serverTimestampBehavior: String?, callbackId: String) { self.reference = reference self.compositeFilter = FirebaseFirestoreHelper.createQueryCompositeFilterConstraintFromJSObject(compositeFilter) self.queryConstraints = FirebaseFirestoreHelper.createQueryNonFilterConstraintArrayFromJSArray(queryConstraints) self.includeMetadataChanges = includeMetadataChanges + self.serverTimestampBehavior = serverTimestampBehavior self.callbackId = callbackId } @@ -32,6 +34,10 @@ import Capacitor return includeMetadataChanges } + func getServerTimestampBehavior() -> String? { + return serverTimestampBehavior + } + func getCallbackId() -> String { return callbackId } diff --git a/packages/firestore/ios/Plugin/Classes/Options/AddCollectionSnapshotListenerOptions.swift b/packages/firestore/ios/Plugin/Classes/Options/AddCollectionSnapshotListenerOptions.swift index 624b42020..b62e7452e 100644 --- a/packages/firestore/ios/Plugin/Classes/Options/AddCollectionSnapshotListenerOptions.swift +++ b/packages/firestore/ios/Plugin/Classes/Options/AddCollectionSnapshotListenerOptions.swift @@ -6,13 +6,15 @@ import Capacitor private var compositeFilter: QueryCompositeFilterConstraint? private var queryConstraints: [QueryNonFilterConstraint] private var includeMetadataChanges: Bool + private var serverTimestampBehavior: String? private var callbackId: String - init(reference: String, compositeFilter: JSObject?, queryConstraints: [JSObject]?, includeMetadataChanges: Bool, callbackId: String) { + init(reference: String, compositeFilter: JSObject?, queryConstraints: [JSObject]?, includeMetadataChanges: Bool, serverTimestampBehavior: String?, callbackId: String) { self.reference = reference self.compositeFilter = FirebaseFirestoreHelper.createQueryCompositeFilterConstraintFromJSObject(compositeFilter) self.queryConstraints = FirebaseFirestoreHelper.createQueryNonFilterConstraintArrayFromJSArray(queryConstraints) self.includeMetadataChanges = includeMetadataChanges + self.serverTimestampBehavior = serverTimestampBehavior self.callbackId = callbackId } @@ -32,6 +34,10 @@ import Capacitor return includeMetadataChanges } + func getServerTimestampBehavior() -> String? { + return serverTimestampBehavior + } + func getCallbackId() -> String { return callbackId } diff --git a/packages/firestore/ios/Plugin/Classes/Options/AddDocumentSnapshotListenerOptions.swift b/packages/firestore/ios/Plugin/Classes/Options/AddDocumentSnapshotListenerOptions.swift index 1157b1ade..393dd6092 100644 --- a/packages/firestore/ios/Plugin/Classes/Options/AddDocumentSnapshotListenerOptions.swift +++ b/packages/firestore/ios/Plugin/Classes/Options/AddDocumentSnapshotListenerOptions.swift @@ -3,11 +3,13 @@ import Foundation @objc public class AddDocumentSnapshotListenerOptions: NSObject { private var reference: String private var includeMetadataChanges: Bool + private var serverTimestampBehavior: String? private var callbackId: String - init(reference: String, includeMetadataChanges: Bool, callbackId: String) { + init(reference: String, includeMetadataChanges: Bool, serverTimestampBehavior: String?, callbackId: String) { self.reference = reference self.includeMetadataChanges = includeMetadataChanges + self.serverTimestampBehavior = serverTimestampBehavior self.callbackId = callbackId } @@ -19,6 +21,10 @@ import Foundation return includeMetadataChanges } + func getServerTimestampBehavior() -> String? { + return serverTimestampBehavior + } + func getCallbackId() -> String { return callbackId } diff --git a/packages/firestore/ios/Plugin/Classes/Results/GetCollectionGroupResult.swift b/packages/firestore/ios/Plugin/Classes/Results/GetCollectionGroupResult.swift index e220b52a6..6c4993553 100644 --- a/packages/firestore/ios/Plugin/Classes/Results/GetCollectionGroupResult.swift +++ b/packages/firestore/ios/Plugin/Classes/Results/GetCollectionGroupResult.swift @@ -4,15 +4,17 @@ import Capacitor @objc public class GetCollectionGroupResult: NSObject, Result { let querySnapshot: QuerySnapshot + let serverTimestampBehavior: ServerTimestampBehavior - init(_ querySnapshot: QuerySnapshot) { + init(_ querySnapshot: QuerySnapshot, _ serverTimestampBehavior: ServerTimestampBehavior = .none) { self.querySnapshot = querySnapshot + self.serverTimestampBehavior = serverTimestampBehavior } public func toJSObject() -> AnyObject { var snapshotsResult = JSArray() for documentSnapshot in querySnapshot.documents { - let snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromHashMap(documentSnapshot.data()) + let snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromHashMap(documentSnapshot.data(with: serverTimestampBehavior)) var snapshotResult = JSObject() snapshotResult["id"] = documentSnapshot.documentID diff --git a/packages/firestore/ios/Plugin/Classes/Results/GetCollectionResult.swift b/packages/firestore/ios/Plugin/Classes/Results/GetCollectionResult.swift index b259416ec..14f179d1f 100644 --- a/packages/firestore/ios/Plugin/Classes/Results/GetCollectionResult.swift +++ b/packages/firestore/ios/Plugin/Classes/Results/GetCollectionResult.swift @@ -4,15 +4,17 @@ import Capacitor @objc public class GetCollectionResult: NSObject, Result { let querySnapshot: QuerySnapshot + let serverTimestampBehavior: ServerTimestampBehavior - init(_ querySnapshot: QuerySnapshot) { + init(_ querySnapshot: QuerySnapshot, _ serverTimestampBehavior: ServerTimestampBehavior = .none) { self.querySnapshot = querySnapshot + self.serverTimestampBehavior = serverTimestampBehavior } public func toJSObject() -> AnyObject { var snapshotsResult = JSArray() for documentSnapshot in querySnapshot.documents { - let snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromHashMap(documentSnapshot.data()) + let snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromHashMap(documentSnapshot.data(with: serverTimestampBehavior)) var snapshotResult = JSObject() snapshotResult["id"] = documentSnapshot.documentID diff --git a/packages/firestore/ios/Plugin/Classes/Results/GetDocumentResult.swift b/packages/firestore/ios/Plugin/Classes/Results/GetDocumentResult.swift index fa8509a44..3168a849b 100644 --- a/packages/firestore/ios/Plugin/Classes/Results/GetDocumentResult.swift +++ b/packages/firestore/ios/Plugin/Classes/Results/GetDocumentResult.swift @@ -4,13 +4,15 @@ import Capacitor @objc public class GetDocumentResult: NSObject, Result { let documentSnapshot: DocumentSnapshot + let serverTimestampBehavior: ServerTimestampBehavior - init(_ documentSnapshot: DocumentSnapshot) { + init(_ documentSnapshot: DocumentSnapshot, _ serverTimestampBehavior: ServerTimestampBehavior = .none) { self.documentSnapshot = documentSnapshot + self.serverTimestampBehavior = serverTimestampBehavior } public func toJSObject() -> AnyObject { - let snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromHashMap(documentSnapshot.data()) + let snapshotDataResult = FirebaseFirestoreHelper.createJSObjectFromHashMap(documentSnapshot.data(with: serverTimestampBehavior)) var snapshotResult = JSObject() snapshotResult["id"] = documentSnapshot.documentID diff --git a/packages/firestore/ios/Plugin/FirebaseFirestore.swift b/packages/firestore/ios/Plugin/FirebaseFirestore.swift index 23815a8a2..b7a610bc4 100644 --- a/packages/firestore/ios/Plugin/FirebaseFirestore.swift +++ b/packages/firestore/ios/Plugin/FirebaseFirestore.swift @@ -265,13 +265,14 @@ private actor ListenerRegistrationMap { @objc public func addDocumentSnapshotListener(_ options: AddDocumentSnapshotListenerOptions, completion: @escaping (Result?, Error?) -> Void) { let reference = options.getReference() let includeMetadataChanges = options.getIncludeMetadataChanges() + let serverTimestampBehavior = FirebaseFirestoreHelper.createServerTimestampBehavior(options.getServerTimestampBehavior()) let callbackId = options.getCallbackId() let listenerRegistration = getFirestoreInstance().document(reference).addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { documentSnapshot, error in if let error = error { completion(nil, error) } else { - let result = GetDocumentResult(documentSnapshot!) + let result = GetDocumentResult(documentSnapshot!, serverTimestampBehavior) completion(result, nil) } } @@ -285,6 +286,7 @@ private actor ListenerRegistrationMap { let compositeFilter = options.getCompositeFilter() let queryConstraints = options.getQueryConstraints() let includeMetadataChanges = options.getIncludeMetadataChanges() + let serverTimestampBehavior = FirebaseFirestoreHelper.createServerTimestampBehavior(options.getServerTimestampBehavior()) let callbackId = options.getCallbackId() Task { @@ -306,7 +308,7 @@ private actor ListenerRegistrationMap { if let error = error { completion(nil, error) } else { - let result = GetCollectionResult(querySnapshot!) + let result = GetCollectionResult(querySnapshot!, serverTimestampBehavior) completion(result, nil) } } @@ -322,6 +324,7 @@ private actor ListenerRegistrationMap { let compositeFilter = options.getCompositeFilter() let queryConstraints = options.getQueryConstraints() let includeMetadataChanges = options.getIncludeMetadataChanges() + let serverTimestampBehavior = FirebaseFirestoreHelper.createServerTimestampBehavior(options.getServerTimestampBehavior()) let callbackId = options.getCallbackId() Task { @@ -343,7 +346,7 @@ private actor ListenerRegistrationMap { if let error = error { completion(nil, error) } else { - let result = GetCollectionGroupResult(querySnapshot!) + let result = GetCollectionGroupResult(querySnapshot!, serverTimestampBehavior) completion(result, nil) } } diff --git a/packages/firestore/ios/Plugin/FirebaseFirestoreHelper.swift b/packages/firestore/ios/Plugin/FirebaseFirestoreHelper.swift index 622f3fd1f..b0c780a1c 100644 --- a/packages/firestore/ios/Plugin/FirebaseFirestoreHelper.swift +++ b/packages/firestore/ios/Plugin/FirebaseFirestoreHelper.swift @@ -3,6 +3,17 @@ import FirebaseFirestore import Capacitor public class FirebaseFirestoreHelper { + public static func createServerTimestampBehavior(_ value: String?) -> ServerTimestampBehavior { + switch value { + case "estimate": + return .estimate + case "previous": + return .previous + default: + return .none + } + } + public static func createHashMapFromJSObject(_ object: JSObject) -> [String: Any] { var map: [String: Any] = [:] for key in object.keys { diff --git a/packages/firestore/ios/Plugin/FirebaseFirestorePlugin.swift b/packages/firestore/ios/Plugin/FirebaseFirestorePlugin.swift index 15d0787aa..7eedfb261 100644 --- a/packages/firestore/ios/Plugin/FirebaseFirestorePlugin.swift +++ b/packages/firestore/ios/Plugin/FirebaseFirestorePlugin.swift @@ -295,6 +295,7 @@ public class FirebaseFirestorePlugin: CAPPlugin, CAPBridgedPlugin { return } let includeMetadataChanges = call.getBool("includeMetadataChanges", false) + let serverTimestampBehavior = call.getString("serverTimestamps") guard let callbackId = call.callbackId else { call.reject(errorCallbackIdMissing) return @@ -302,7 +303,7 @@ public class FirebaseFirestorePlugin: CAPPlugin, CAPBridgedPlugin { self.pluginCallMap[callbackId] = call - let options = AddDocumentSnapshotListenerOptions(reference: reference, includeMetadataChanges: includeMetadataChanges, callbackId: callbackId) + let options = AddDocumentSnapshotListenerOptions(reference: reference, includeMetadataChanges: includeMetadataChanges, serverTimestampBehavior: serverTimestampBehavior, callbackId: callbackId) implementation?.addDocumentSnapshotListener(options, completion: { result, error in if let error = error { @@ -326,6 +327,7 @@ public class FirebaseFirestorePlugin: CAPPlugin, CAPBridgedPlugin { let compositeFilter = call.getObject("compositeFilter") let queryConstraints = call.getArray("queryConstraints", JSObject.self) let includeMetadataChanges = call.getBool("includeMetadataChanges", false) + let serverTimestampBehavior = call.getString("serverTimestamps") guard let callbackId = call.callbackId else { call.reject(errorCallbackIdMissing) return @@ -333,7 +335,7 @@ public class FirebaseFirestorePlugin: CAPPlugin, CAPBridgedPlugin { self.pluginCallMap[callbackId] = call - let options = AddCollectionSnapshotListenerOptions(reference: reference, compositeFilter: compositeFilter, queryConstraints: queryConstraints, includeMetadataChanges: includeMetadataChanges, callbackId: callbackId) + let options = AddCollectionSnapshotListenerOptions(reference: reference, compositeFilter: compositeFilter, queryConstraints: queryConstraints, includeMetadataChanges: includeMetadataChanges, serverTimestampBehavior: serverTimestampBehavior, callbackId: callbackId) do { implementation?.addCollectionSnapshotListener(options, completion: { result, error in @@ -362,6 +364,7 @@ public class FirebaseFirestorePlugin: CAPPlugin, CAPBridgedPlugin { let compositeFilter = call.getObject("compositeFilter") let queryConstraints = call.getArray("queryConstraints", JSObject.self) let includeMetadataChanges = call.getBool("includeMetadataChanges", false) + let serverTimestampBehavior = call.getString("serverTimestamps") guard let callbackId = call.callbackId else { call.reject(errorCallbackIdMissing) return @@ -369,7 +372,7 @@ public class FirebaseFirestorePlugin: CAPPlugin, CAPBridgedPlugin { self.pluginCallMap[callbackId] = call - let options = AddCollectionGroupSnapshotListenerOptions(reference: reference, compositeFilter: compositeFilter, queryConstraints: queryConstraints, includeMetadataChanges: includeMetadataChanges, callbackId: callbackId) + let options = AddCollectionGroupSnapshotListenerOptions(reference: reference, compositeFilter: compositeFilter, queryConstraints: queryConstraints, includeMetadataChanges: includeMetadataChanges, serverTimestampBehavior: serverTimestampBehavior, callbackId: callbackId) do { implementation?.addCollectionGroupSnapshotListener(options, completion: { result, error in diff --git a/packages/firestore/src/definitions.ts b/packages/firestore/src/definitions.ts index cd1b1b783..2539a7c0c 100644 --- a/packages/firestore/src/definitions.ts +++ b/packages/firestore/src/definitions.ts @@ -452,6 +452,20 @@ export interface SnapshotListenerOptions { * @default "default" */ readonly source?: 'default' | 'cache'; + /** + * Control how pending server timestamps are returned in snapshots + * that have not yet been acknowledged by the server. + * + * - `none`: pending server timestamps are returned as `null`. + * - `estimate`: pending server timestamps are replaced with the + * local client's time estimate. + * - `previous`: pending server timestamps are replaced with the + * previous value of the field (or `null` if there is no previous value). + * + * @since 8.3.0 + * @default "none" + */ + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; } /** diff --git a/packages/firestore/src/web.ts b/packages/firestore/src/web.ts index 3319d6857..33c642225 100644 --- a/packages/firestore/src/web.ts +++ b/packages/firestore/src/web.ts @@ -91,6 +91,12 @@ import { FieldValue } from './field-value'; import { GeoPoint } from './geopoint'; import { Timestamp } from './timestamp'; +type ServerTimestamps = 'estimate' | 'previous' | 'none'; + +function normalizeServerTimestamps(value: unknown): ServerTimestamps { + return value === 'estimate' || value === 'previous' ? value : 'none'; +} + export class FirebaseFirestoreWeb extends WebPlugin implements FirebaseFirestorePlugin @@ -118,7 +124,13 @@ export class FirebaseFirestoreWeb snapshots: snapshot.docs.map(documentSnapshot => ({ id: documentSnapshot.id, path: documentSnapshot.ref.path, - data: this.deserializeData(documentSnapshot.data()) as T, + data: this.deserializeData( + documentSnapshot.data({ + serverTimestamps: normalizeServerTimestamps( + options.serverTimestamps, + ), + }), + ) as T, metadata: { hasPendingWrites: documentSnapshot.metadata.hasPendingWrites, fromCache: documentSnapshot.metadata.fromCache, @@ -155,7 +167,13 @@ export class FirebaseFirestoreWeb snapshots: snapshot.docs.map(documentSnapshot => ({ id: documentSnapshot.id, path: documentSnapshot.ref.path, - data: this.deserializeData(documentSnapshot.data()) as T, + data: this.deserializeData( + documentSnapshot.data({ + serverTimestamps: normalizeServerTimestamps( + options.serverTimestamps, + ), + }), + ) as T, metadata: { hasPendingWrites: documentSnapshot.metadata.hasPendingWrites, fromCache: documentSnapshot.metadata.fromCache, @@ -202,7 +220,9 @@ export class FirebaseFirestoreWeb source: options.source, }, snapshot => { - const data = snapshot.data(); + const data = snapshot.data({ + serverTimestamps: normalizeServerTimestamps(options.serverTimestamps), + }); const event: AddDocumentSnapshotListenerCallbackEvent = { snapshot: { id: snapshot.id,