diff --git a/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreModule.java b/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreModule.java index 1b25ac8551..aa611a745c 100644 --- a/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreModule.java +++ b/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreModule.java @@ -83,6 +83,10 @@ Task clearPersistence(String appName) { return getFirestoreForApp(appName).clearPersistence(); } + Task waitForPendingWrites(String appName) { + return getFirestoreForApp(appName).waitForPendingWrites(); + } + Task terminate(String appName) { FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreModule.java index b2f5f9747d..8383d40e09 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreModule.java @@ -56,6 +56,17 @@ public void clearPersistence(String appName, Promise promise) { }); } + @ReactMethod + public void waitForPendingWrites(String appName, Promise promise) { + module.waitForPendingWrites(appName).addOnCompleteListener(task -> { + if (task.isSuccessful()) { + promise.resolve(null); + } else { + rejectPromiseFirestoreException(promise, task.getException()); + } + }); + } + @ReactMethod public void disableNetwork(String appName, Promise promise) { module.disableNetwork(appName).addOnCompleteListener(task -> { diff --git a/packages/firestore/e2e/firestore.e2e.js b/packages/firestore/e2e/firestore.e2e.js index c5c4b9b7c3..77e41950a1 100644 --- a/packages/firestore/e2e/firestore.e2e.js +++ b/packages/firestore/e2e/firestore.e2e.js @@ -364,4 +364,60 @@ describe('firestore()', () => { } }); }); + + describe('wait for pending writes', () => { + it('waits for pending writes', async () => { + const waitForPromiseMs = 500; + const testTimeoutMs = 10000; + + await firebase.firestore().disableNetwork(); + + //set up a pending write + + const db = firebase.firestore(); + const id = 'foobar'; + const ref = db.doc(`v6/${id}`); + ref.set({ foo: 'bar' }); + + //waitForPendingWrites should never resolve, but unfortunately we can only + //test that this is not returning within X ms + + let rejected = false; + const timedOutWithNetworkDisabled = await Promise.race([ + firebase + .firestore() + .waitForPendingWrites() + .then( + () => false, + () => { + rejected = true; + }, + ), + Utils.sleep(waitForPromiseMs).then(() => true), + ]); + + should(timedOutWithNetworkDisabled).equal(true); + should(rejected).equal(false); + + //if we sign in as a different user then it should reject the promise + try { + await firebase.auth().signOut(); + } catch (e) {} + await firebase.auth().signInAnonymously(); + should(rejected).equal(true); + + //now if we enable the network then waitForPendingWrites should return immediately + await firebase.firestore().enableNetwork(); + + const timedOutWithNetworkEnabled = await Promise.race([ + firebase + .firestore() + .waitForPendingWrites() + .then(() => false), + Utils.sleep(testTimeoutMs).then(() => true), + ]); + + should(timedOutWithNetworkEnabled).equal(false); + }); + }); }); diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m index e0feef6625..1240c0a8e1 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m @@ -117,6 +117,20 @@ + (BOOL)requiresMainQueueSetup { }]; } +RCT_EXPORT_METHOD(waitForPendingWrites: + (FIRApp *) firebaseApp + : (RCTPromiseResolveBlock) resolve + : (RCTPromiseRejectBlock)reject +) { + [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] waitForPendingWritesWithCompletion:^(NSError *error) { + if (error) { + [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; + } else { + resolve(nil); + } + }]; +} + RCT_EXPORT_METHOD(terminate: (FIRApp *) firebaseApp : (RCTPromiseResolveBlock) resolve diff --git a/packages/firestore/lib/index.d.ts b/packages/firestore/lib/index.d.ts index 3b6cada872..bf7c5e3233 100644 --- a/packages/firestore/lib/index.d.ts +++ b/packages/firestore/lib/index.d.ts @@ -1969,6 +1969,24 @@ export namespace FirebaseFirestoreTypes { * ``` */ clearPersistence(): Promise; + /** + * Waits until all currently pending writes for the active user have been acknowledged by the + * backend. + * + * The returned Promise resolves immediately if there are no outstanding writes. Otherwise, the + * Promise waits for all previously issued writes (including those written in a previous app + * session), but it does not wait for writes that were added after the method is called. If you + * want to wait for additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` Promises are rejected when the logged-in user changes. + * + * #### Example + * + *```js + * await firebase.firestore().waitForPendingWrites(); + * ``` + */ + waitForPendingWrites(): Promise; /** * Typically called to ensure a new Firestore instance is initialized before calling * `firebase.firestore().clearPersistence()`. diff --git a/packages/firestore/lib/index.js b/packages/firestore/lib/index.js index cc1d411b36..853c6a341e 100644 --- a/packages/firestore/lib/index.js +++ b/packages/firestore/lib/index.js @@ -84,6 +84,10 @@ class FirebaseFirestoreModule extends FirebaseModule { await this.native.clearPersistence(); } + async waitForPendingWrites() { + await this.native.waitForPendingWrites(); + } + async terminate() { await this.native.terminate(); }