From eec08a06f41dd96d13778fbed2afcaaac238fca4 Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Fri, 9 Aug 2024 09:12:20 +0100 Subject: [PATCH 1/5] feat(firestore): support for second database (#7949) --- .github/workflows/scripts/firestore.rules | 10 +- .../app/lib/internal/registry/namespace.js | 16 +- .../app/lib/internal/registry/nativeModule.js | 5 +- .../UniversalFirebaseFirestoreCommon.java | 26 +- .../UniversalFirebaseFirestoreModule.java | 54 +- ...tiveFirebaseFirestoreCollectionModule.java | 75 +- .../ReactNativeFirebaseFirestoreCommon.java | 7 +- ...NativeFirebaseFirestoreDocumentModule.java | 56 +- .../ReactNativeFirebaseFirestoreEvent.java | 6 +- .../ReactNativeFirebaseFirestoreModule.java | 35 +- .../ReactNativeFirebaseFirestoreQuery.java | 4 +- ...ReactNativeFirebaseFirestoreSerialize.java | 21 +- ...iveFirebaseFirestoreTransactionModule.java | 19 +- .../SecondDatabase/second.Transation.e2e.js | 774 ++++++++++++ .../SecondDatabase/second.onSnapshot.e2e.js | 696 +++++++++++ .../e2e/SecondDatabase/second.where.e2e.js | 1095 +++++++++++++++++ packages/firestore/e2e/helpers.js | 4 +- .../RNFBFirestoreCollectionModule.m | 127 +- .../ios/RNFBFirestore/RNFBFirestoreCommon.h | 8 +- .../ios/RNFBFirestore/RNFBFirestoreCommon.m | 33 +- .../RNFBFirestoreDocumentModule.m | 52 +- .../ios/RNFBFirestore/RNFBFirestoreModule.m | 48 +- .../RNFBFirestore/RNFBFirestoreSerialize.h | 8 +- .../RNFBFirestore/RNFBFirestoreSerialize.m | 27 +- .../RNFBFirestoreTransactionModule.m | 28 +- packages/firestore/lib/index.d.ts | 2 +- packages/firestore/lib/index.js | 13 +- packages/firestore/lib/modular/index.d.ts | 12 + packages/firestore/lib/modular/index.js | 14 +- .../firestore/lib/web/RNFBFirestoreModule.js | 91 +- .../ReactNativeFirebaseStorageModule.java | 3 +- tests/app.js | 1 + tests/ios/Podfile.lock | 76 +- 33 files changed, 3137 insertions(+), 309 deletions(-) create mode 100644 packages/firestore/e2e/SecondDatabase/second.Transation.e2e.js create mode 100644 packages/firestore/e2e/SecondDatabase/second.onSnapshot.e2e.js create mode 100644 packages/firestore/e2e/SecondDatabase/second.where.e2e.js diff --git a/.github/workflows/scripts/firestore.rules b/.github/workflows/scripts/firestore.rules index 84924b7e66..d3220037de 100644 --- a/.github/workflows/scripts/firestore.rules +++ b/.github/workflows/scripts/firestore.rules @@ -9,9 +9,13 @@ service cloud.firestore { } match /firestore/{document=**} { allow read, write: if true; - } + } match /{path=**}/collectionGroup/{documentId} { allow read, write: if true; - } + } + match /second-database/{document=**} { + // separate rules are not supported so we need to use the same rules for both databases to prove it is querying different databases + allow read, write: if database == "second-rnfb"; + } } -} \ No newline at end of file +} diff --git a/packages/app/lib/internal/registry/namespace.js b/packages/app/lib/internal/registry/namespace.js index b57ba3496a..d8a41903e6 100644 --- a/packages/app/lib/internal/registry/namespace.js +++ b/packages/app/lib/internal/registry/namespace.js @@ -15,7 +15,7 @@ * */ -import { isString } from '@react-native-firebase/app/lib/common'; +import { isString } from '../../common'; import FirebaseApp from '../../FirebaseApp'; import SDK_VERSION from '../../version'; import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants'; @@ -93,19 +93,21 @@ function getOrCreateModuleForApp(app, moduleNamespace) { ); } - // e.g. firebase.storage(customUrlOrRegion) - function firebaseModuleWithArgs(customUrlOrRegion) { - if (customUrlOrRegion !== undefined) { + // e.g. firebase.storage(customUrlOrRegion), firebase.functions(customUrlOrRegion), firebase.firestore(databaseId), firebase.database(url) + function firebaseModuleWithArgs(customUrlOrRegionOrDatabaseId) { + if (customUrlOrRegionOrDatabaseId !== undefined) { if (!hasCustomUrlOrRegionSupport) { // TODO throw Module does not support arguments error } - if (!isString(customUrlOrRegion)) { + if (!isString(customUrlOrRegionOrDatabaseId)) { // TODO throw Module first argument must be a string error } } - const key = customUrlOrRegion ? `${customUrlOrRegion}:${moduleNamespace}` : moduleNamespace; + const key = customUrlOrRegionOrDatabaseId + ? `${customUrlOrRegionOrDatabaseId}:${moduleNamespace}` + : moduleNamespace; if (!APP_MODULE_INSTANCE[app.name]) { APP_MODULE_INSTANCE[app.name] = {}; @@ -115,7 +117,7 @@ function getOrCreateModuleForApp(app, moduleNamespace) { APP_MODULE_INSTANCE[app.name][key] = new ModuleClass( app, NAMESPACE_REGISTRY[moduleNamespace], - customUrlOrRegion, + customUrlOrRegionOrDatabaseId, ); } diff --git a/packages/app/lib/internal/registry/nativeModule.js b/packages/app/lib/internal/registry/nativeModule.js index 5657ef8ce3..2778b2dd75 100644 --- a/packages/app/lib/internal/registry/nativeModule.js +++ b/packages/app/lib/internal/registry/nativeModule.js @@ -153,7 +153,10 @@ function initialiseNativeModule(module) { function subscribeToNativeModuleEvent(eventName) { if (!NATIVE_MODULE_EVENT_SUBSCRIPTIONS[eventName]) { RNFBNativeEventEmitter.addListener(eventName, event => { - if (event.appName) { + if (event.appName && event.databaseId) { + // Firestore requires both appName and databaseId to prefix + SharedEventEmitter.emit(`${event.appName}-${event.databaseId}-${eventName}`, event); + } else if (event.appName) { // native event has an appName property - auto prefix and internally emit SharedEventEmitter.emit(`${event.appName}-${eventName}`, event); } else { diff --git a/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreCommon.java b/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreCommon.java index d217a42aae..876759eea1 100644 --- a/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreCommon.java +++ b/packages/firestore/android/src/main/java/io/invertase/firebase/firestore/UniversalFirebaseFirestoreCommon.java @@ -29,8 +29,13 @@ public class UniversalFirebaseFirestoreCommon { static WeakHashMap> instanceCache = new WeakHashMap<>(); - static FirebaseFirestore getFirestoreForApp(String appName) { - WeakReference cachedInstance = instanceCache.get(appName); + static String createFirestoreKey(String appName, String databaseId) { + return appName + ":" + databaseId; + } + + static FirebaseFirestore getFirestoreForApp(String appName, String databaseId) { + String firestoreKey = createFirestoreKey(appName, databaseId); + WeakReference cachedInstance = instanceCache.get(firestoreKey); if (cachedInstance != null) { return cachedInstance.get(); @@ -38,24 +43,27 @@ static FirebaseFirestore getFirestoreForApp(String appName) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); - FirebaseFirestore instance = FirebaseFirestore.getInstance(firebaseApp); + FirebaseFirestore instance = FirebaseFirestore.getInstance(firebaseApp, databaseId); - setFirestoreSettings(instance, appName); + setFirestoreSettings(instance, firestoreKey); instanceCache.put(appName, new WeakReference(instance)); return instance; } - private static void setFirestoreSettings(FirebaseFirestore firebaseFirestore, String appName) { + private static void setFirestoreSettings( + FirebaseFirestore firebaseFirestore, String firestoreKey) { UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); FirebaseFirestoreSettings.Builder firestoreSettings = new FirebaseFirestoreSettings.Builder(); - String cacheSizeKey = UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + appName; - String hostKey = UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + appName; - String persistenceKey = UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + appName; - String sslKey = UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName; + String cacheSizeKey = + UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + firestoreKey; + String hostKey = UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + firestoreKey; + String persistenceKey = + UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + firestoreKey; + String sslKey = UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + firestoreKey; int cacheSizeBytes = preferences.getIntValue( 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 1fcacfaffb..77345d3529 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 @@ -17,6 +17,7 @@ * */ +import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.createFirestoreKey; import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.getFirestoreForApp; import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.instanceCache; @@ -40,27 +41,28 @@ public class UniversalFirebaseFirestoreModule extends UniversalFirebaseModule { super(context, serviceName); } - Task disableNetwork(String appName) { - return getFirestoreForApp(appName).disableNetwork(); + Task disableNetwork(String appName, String databaseId) { + return getFirestoreForApp(appName, databaseId).disableNetwork(); } - Task enableNetwork(String appName) { - return getFirestoreForApp(appName).enableNetwork(); + Task enableNetwork(String appName, String databaseId) { + return getFirestoreForApp(appName, databaseId).enableNetwork(); } - Task useEmulator(String appName, String host, int port) { + Task useEmulator(String appName, String databaseId, String host, int port) { return Tasks.call( getExecutor(), () -> { - if (emulatorConfigs.get(appName) == null) { - emulatorConfigs.put(appName, "true"); - getFirestoreForApp(appName).useEmulator(host, port); + String firestoreKey = createFirestoreKey(appName, databaseId); + if (emulatorConfigs.get(firestoreKey) == null) { + emulatorConfigs.put(firestoreKey, "true"); + getFirestoreForApp(appName, databaseId).useEmulator(host, port); } return null; }); } - Task settings(String appName, Map settings) { + Task settings(String firestoreKey, Map settings) { return Tasks.call( getExecutor(), () -> { @@ -70,7 +72,7 @@ Task settings(String appName, Map settings) { UniversalFirebasePreferences.getSharedInstance() .setIntValue( - UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + appName, + UniversalFirebaseFirestoreStatics.FIRESTORE_CACHE_SIZE + "_" + firestoreKey, Objects.requireNonNull(cacheSizeBytesDouble).intValue()); } @@ -78,7 +80,7 @@ Task settings(String appName, Map settings) { if (settings.containsKey("host")) { UniversalFirebasePreferences.getSharedInstance() .setStringValue( - UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + appName, + UniversalFirebaseFirestoreStatics.FIRESTORE_HOST + "_" + firestoreKey, (String) settings.get("host")); } @@ -86,7 +88,7 @@ Task settings(String appName, Map settings) { if (settings.containsKey("persistence")) { UniversalFirebasePreferences.getSharedInstance() .setBooleanValue( - UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + appName, + UniversalFirebaseFirestoreStatics.FIRESTORE_PERSISTENCE + "_" + firestoreKey, (boolean) settings.get("persistence")); } @@ -94,7 +96,7 @@ Task settings(String appName, Map settings) { if (settings.containsKey("ssl")) { UniversalFirebasePreferences.getSharedInstance() .setBooleanValue( - UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName, + UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + firestoreKey, (boolean) settings.get("ssl")); } @@ -104,7 +106,7 @@ Task settings(String appName, Map settings) { .setStringValue( UniversalFirebaseFirestoreStatics.FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR + "_" - + appName, + + firestoreKey, (String) settings.get("serverTimestampBehavior")); } @@ -112,25 +114,25 @@ Task settings(String appName, Map settings) { }); } - LoadBundleTask loadBundle(String appName, String bundle) { + LoadBundleTask loadBundle(String appName, String databaseId, String bundle) { byte[] bundleData = bundle.getBytes(StandardCharsets.UTF_8); - return getFirestoreForApp(appName).loadBundle(bundleData); + return getFirestoreForApp(appName, databaseId).loadBundle(bundleData); } - Task clearPersistence(String appName) { - return getFirestoreForApp(appName).clearPersistence(); + Task clearPersistence(String appName, String databaseId) { + return getFirestoreForApp(appName, databaseId).clearPersistence(); } - Task waitForPendingWrites(String appName) { - return getFirestoreForApp(appName).waitForPendingWrites(); + Task waitForPendingWrites(String appName, String databaseId) { + return getFirestoreForApp(appName, databaseId).waitForPendingWrites(); } - Task terminate(String appName) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); - - if (instanceCache.get(appName) != null) { - instanceCache.get(appName).clear(); - instanceCache.remove(appName); + Task terminate(String appName, String databaseId) { + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); + String firestoreKey = createFirestoreKey(appName, databaseId); + if (instanceCache.get(firestoreKey) != null) { + instanceCache.get(firestoreKey).clear(); + instanceCache.remove(firestoreKey); } return firebaseFirestore.terminate(); diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java index 84f15c5906..982e38680c 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java @@ -53,6 +53,7 @@ public void onCatalystInstanceDestroy() { @ReactMethod public void namedQueryOnSnapshot( String appName, + String databaseId, String queryName, String type, ReadableArray filters, @@ -64,7 +65,7 @@ public void namedQueryOnSnapshot( return; } - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); firebaseFirestore .getNamedQuery(queryName) .addOnCompleteListener( @@ -72,15 +73,16 @@ public void namedQueryOnSnapshot( if (task.isSuccessful()) { Query query = task.getResult(); if (query == null) { - sendOnSnapshotError(appName, listenerId, new NullPointerException()); + sendOnSnapshotError(appName, databaseId, listenerId, new NullPointerException()); } else { ReactNativeFirebaseFirestoreQuery firestoreQuery = new ReactNativeFirebaseFirestoreQuery( - appName, query, filters, orders, options); - handleQueryOnSnapshot(firestoreQuery, appName, listenerId, listenerOptions); + appName, databaseId, query, filters, orders, options); + handleQueryOnSnapshot( + firestoreQuery, appName, databaseId, listenerId, listenerOptions); } } else { - sendOnSnapshotError(appName, listenerId, task.getException()); + sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); } }); } @@ -88,6 +90,7 @@ public void namedQueryOnSnapshot( @ReactMethod public void collectionOnSnapshot( String appName, + String databaseId, String path, String type, ReadableArray filters, @@ -99,16 +102,21 @@ public void collectionOnSnapshot( return; } - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); ReactNativeFirebaseFirestoreQuery firestoreQuery = new ReactNativeFirebaseFirestoreQuery( - appName, getQueryForFirestore(firebaseFirestore, path, type), filters, orders, options); - - handleQueryOnSnapshot(firestoreQuery, appName, listenerId, listenerOptions); + appName, + databaseId, + getQueryForFirestore(firebaseFirestore, path, type), + filters, + orders, + options); + + handleQueryOnSnapshot(firestoreQuery, appName, databaseId, listenerId, listenerOptions); } @ReactMethod - public void collectionOffSnapshot(String appName, int listenerId) { + public void collectionOffSnapshot(String appName, String databaseId, int listenerId) { ListenerRegistration listenerRegistration = collectionSnapshotListeners.get(listenerId); if (listenerRegistration != null) { listenerRegistration.remove(); @@ -120,6 +128,7 @@ public void collectionOffSnapshot(String appName, int listenerId) { @ReactMethod public void namedQueryGet( String appName, + String databaseId, String queryName, String type, ReadableArray filters, @@ -127,7 +136,7 @@ public void namedQueryGet( ReadableMap options, ReadableMap getOptions, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); firebaseFirestore .getNamedQuery(queryName) .addOnCompleteListener( @@ -139,7 +148,7 @@ public void namedQueryGet( } else { ReactNativeFirebaseFirestoreQuery firestoreQuery = new ReactNativeFirebaseFirestoreQuery( - appName, query, filters, orders, options); + appName, databaseId, query, filters, orders, options); handleQueryGet(firestoreQuery, getSource(getOptions), promise); } } else { @@ -151,16 +160,22 @@ public void namedQueryGet( @ReactMethod public void collectionCount( String appName, + String databaseId, String path, String type, ReadableArray filters, ReadableArray orders, ReadableMap options, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); ReactNativeFirebaseFirestoreQuery firestoreQuery = new ReactNativeFirebaseFirestoreQuery( - appName, getQueryForFirestore(firebaseFirestore, path, type), filters, orders, options); + appName, + databaseId, + getQueryForFirestore(firebaseFirestore, path, type), + filters, + orders, + options); AggregateQuery aggregateQuery = firestoreQuery.query.count(); @@ -181,6 +196,7 @@ public void collectionCount( @ReactMethod public void collectionGet( String appName, + String databaseId, String path, String type, ReadableArray filters, @@ -188,16 +204,22 @@ public void collectionGet( ReadableMap options, ReadableMap getOptions, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); ReactNativeFirebaseFirestoreQuery firestoreQuery = new ReactNativeFirebaseFirestoreQuery( - appName, getQueryForFirestore(firebaseFirestore, path, type), filters, orders, options); + appName, + databaseId, + getQueryForFirestore(firebaseFirestore, path, type), + filters, + orders, + options); handleQueryGet(firestoreQuery, getSource(getOptions), promise); } private void handleQueryOnSnapshot( ReactNativeFirebaseFirestoreQuery firestoreQuery, String appName, + String databaseId, int listenerId, ReadableMap listenerOptions) { MetadataChanges metadataChanges; @@ -218,9 +240,9 @@ private void handleQueryOnSnapshot( listenerRegistration.remove(); collectionSnapshotListeners.remove(listenerId); } - sendOnSnapshotError(appName, listenerId, exception); + sendOnSnapshotError(appName, databaseId, listenerId, exception); } else { - sendOnSnapshotEvent(appName, listenerId, querySnapshot, metadataChanges); + sendOnSnapshotEvent(appName, databaseId, listenerId, querySnapshot, metadataChanges); } }; @@ -246,12 +268,15 @@ private void handleQueryGet( private void sendOnSnapshotEvent( String appName, + String databaseId, int listenerId, QuerySnapshot querySnapshot, MetadataChanges metadataChanges) { Tasks.call( getTransactionalExecutor(Integer.toString(listenerId)), - () -> snapshotToWritableMap(appName, "onSnapshot", querySnapshot, metadataChanges)) + () -> + snapshotToWritableMap( + appName, databaseId, "onSnapshot", querySnapshot, metadataChanges)) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -266,14 +291,16 @@ private void sendOnSnapshotEvent( ReactNativeFirebaseFirestoreEvent.COLLECTION_EVENT_SYNC, body, appName, + databaseId, listenerId)); } else { - sendOnSnapshotError(appName, listenerId, task.getException()); + sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); } }); } - private void sendOnSnapshotError(String appName, int listenerId, Exception exception) { + private void sendOnSnapshotError( + String appName, String databaseId, int listenerId, Exception exception) { WritableMap body = Arguments.createMap(); WritableMap error = Arguments.createMap(); @@ -293,7 +320,11 @@ private void sendOnSnapshotError(String appName, int listenerId, Exception excep emitter.sendEvent( new ReactNativeFirebaseFirestoreEvent( - ReactNativeFirebaseFirestoreEvent.COLLECTION_EVENT_SYNC, body, appName, listenerId)); + ReactNativeFirebaseFirestoreEvent.COLLECTION_EVENT_SYNC, + body, + appName, + databaseId, + listenerId)); } private Source getSource(ReadableMap getOptions) { diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCommon.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCommon.java index 021400d088..00b714a893 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCommon.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCommon.java @@ -19,6 +19,7 @@ import static io.invertase.firebase.common.ReactNativeFirebaseModule.rejectPromiseWithCodeAndMessage; import static io.invertase.firebase.common.ReactNativeFirebaseModule.rejectPromiseWithExceptionMap; +import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.createFirestoreKey; import com.facebook.react.bridge.Promise; import com.google.firebase.firestore.DocumentSnapshot; @@ -48,10 +49,12 @@ static void rejectPromiseFirestoreException(Promise promise, Exception exception } } - static DocumentSnapshot.ServerTimestampBehavior getServerTimestampBehavior(String appName) { + static DocumentSnapshot.ServerTimestampBehavior getServerTimestampBehavior( + String appName, String databaseId) { + String firestoreKey = createFirestoreKey(appName, databaseId); UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance(); String key = - UniversalFirebaseFirestoreStatics.FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR + "_" + appName; + UniversalFirebaseFirestoreStatics.FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR + "_" + firestoreKey; String behavior = preferences.getStringValue(key, "none"); if ("estimate".equals(behavior)) { diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java index 07fca6fc49..e99531b3f9 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java @@ -56,12 +56,12 @@ public void onCatalystInstanceDestroy() { @ReactMethod public void documentOnSnapshot( - String appName, String path, int listenerId, ReadableMap listenerOptions) { + String appName, String databaseId, String path, int listenerId, ReadableMap listenerOptions) { if (documentSnapshotListeners.get(listenerId) != null) { return; } - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); final EventListener listener = @@ -72,9 +72,9 @@ public void documentOnSnapshot( listenerRegistration.remove(); documentSnapshotListeners.remove(listenerId); } - sendOnSnapshotError(appName, listenerId, exception); + sendOnSnapshotError(appName, databaseId, listenerId, exception); } else { - sendOnSnapshotEvent(appName, listenerId, documentSnapshot); + sendOnSnapshotEvent(appName, databaseId, listenerId, documentSnapshot); } }; @@ -95,7 +95,7 @@ public void documentOnSnapshot( } @ReactMethod - public void documentOffSnapshot(String appName, int listenerId) { + public void documentOffSnapshot(String appName, String databaseId, int listenerId) { ListenerRegistration listenerRegistration = documentSnapshotListeners.get(listenerId); if (listenerRegistration != null) { listenerRegistration.remove(); @@ -104,8 +104,9 @@ public void documentOffSnapshot(String appName, int listenerId) { } @ReactMethod - public void documentGet(String appName, String path, ReadableMap getOptions, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + public void documentGet( + String appName, String databaseId, String path, ReadableMap getOptions, Promise promise) { + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); Source source; @@ -127,7 +128,7 @@ public void documentGet(String appName, String path, ReadableMap getOptions, Pro getExecutor(), () -> { DocumentSnapshot documentSnapshot = Tasks.await(documentReference.get(source)); - return snapshotToWritableMap(appName, documentSnapshot); + return snapshotToWritableMap(appName, databaseId, documentSnapshot); }) .addOnCompleteListener( task -> { @@ -140,8 +141,8 @@ public void documentGet(String appName, String path, ReadableMap getOptions, Pro } @ReactMethod - public void documentDelete(String appName, String path, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + public void documentDelete(String appName, String databaseId, String path, Promise promise) { + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); Tasks.call(getTransactionalExecutor(), documentReference::delete) .addOnCompleteListener( @@ -156,8 +157,13 @@ public void documentDelete(String appName, String path, Promise promise) { @ReactMethod public void documentSet( - String appName, String path, ReadableMap data, ReadableMap options, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + String appName, + String databaseId, + String path, + ReadableMap data, + ReadableMap options, + Promise promise) { + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); Tasks.call(getTransactionalExecutor(), () -> parseReadableMap(firebaseFirestore, data)) @@ -195,8 +201,9 @@ public void documentSet( } @ReactMethod - public void documentUpdate(String appName, String path, ReadableMap data, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + public void documentUpdate( + String appName, String databaseId, String path, ReadableMap data, Promise promise) { + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); Tasks.call(getTransactionalExecutor(), () -> parseReadableMap(firebaseFirestore, data)) @@ -214,8 +221,9 @@ public void documentUpdate(String appName, String path, ReadableMap data, Promis } @ReactMethod - public void documentBatch(String appName, ReadableArray writes, Promise promise) { - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + public void documentBatch( + String appName, String databaseId, ReadableArray writes, Promise promise) { + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); Tasks.call(getTransactionalExecutor(), () -> parseDocumentBatches(firebaseFirestore, writes)) .continueWithTask( @@ -282,8 +290,8 @@ public void documentBatch(String appName, ReadableArray writes, Promise promise) } private void sendOnSnapshotEvent( - String appName, int listenerId, DocumentSnapshot documentSnapshot) { - Tasks.call(getExecutor(), () -> snapshotToWritableMap(appName, documentSnapshot)) + String appName, String databaseId, int listenerId, DocumentSnapshot documentSnapshot) { + Tasks.call(getExecutor(), () -> snapshotToWritableMap(appName, databaseId, documentSnapshot)) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -298,14 +306,16 @@ private void sendOnSnapshotEvent( ReactNativeFirebaseFirestoreEvent.DOCUMENT_EVENT_SYNC, body, appName, + databaseId, listenerId)); } else { - sendOnSnapshotError(appName, listenerId, task.getException()); + sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); } }); } - private void sendOnSnapshotError(String appName, int listenerId, Exception exception) { + private void sendOnSnapshotError( + String appName, String databaseId, int listenerId, Exception exception) { WritableMap body = Arguments.createMap(); WritableMap error = Arguments.createMap(); @@ -325,6 +335,10 @@ private void sendOnSnapshotError(String appName, int listenerId, Exception excep emitter.sendEvent( new ReactNativeFirebaseFirestoreEvent( - ReactNativeFirebaseFirestoreEvent.DOCUMENT_EVENT_SYNC, body, appName, listenerId)); + ReactNativeFirebaseFirestoreEvent.DOCUMENT_EVENT_SYNC, + body, + appName, + databaseId, + listenerId)); } } diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreEvent.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreEvent.java index 614172423e..535c1e0998 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreEvent.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreEvent.java @@ -30,16 +30,19 @@ public class ReactNativeFirebaseFirestoreEvent implements NativeEvent { private static final String KEY_BODY = "body"; private static final String KEY_APP_NAME = "appName"; private static final String KEY_EVENT_NAME = "eventName"; + private static final String DATABASE_ID = "databaseId"; private String eventName; private WritableMap eventBody; private String appName; + private String databaseId; private int listenerId; ReactNativeFirebaseFirestoreEvent( - String eventName, WritableMap eventBody, String appName, int listenerId) { + String eventName, WritableMap eventBody, String appName, String databaseId, int listenerId) { this.eventName = eventName; this.eventBody = eventBody; this.appName = appName; + this.databaseId = databaseId; this.listenerId = listenerId; } @@ -54,6 +57,7 @@ public WritableMap getEventBody() { event.putInt(KEY_ID, listenerId); event.putMap(KEY_BODY, eventBody); event.putString(KEY_APP_NAME, appName); + event.putString(DATABASE_ID, databaseId); event.putString(KEY_EVENT_NAME, eventName); return event; } 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 323188ee47..d3405533ef 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 @@ -19,6 +19,7 @@ import static io.invertase.firebase.common.RCTConvertFirebase.toHashMap; import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreCommon.rejectPromiseFirestoreException; +import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.createFirestoreKey; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; @@ -49,9 +50,9 @@ public void setLogLevel(String logLevel) { } @ReactMethod - public void loadBundle(String appName, String bundle, Promise promise) { + public void loadBundle(String appName, String databaseId, String bundle, Promise promise) { module - .loadBundle(appName, bundle) + .loadBundle(appName, databaseId, bundle) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -64,9 +65,9 @@ public void loadBundle(String appName, String bundle, Promise promise) { } @ReactMethod - public void clearPersistence(String appName, Promise promise) { + public void clearPersistence(String appName, String databaseId, Promise promise) { module - .clearPersistence(appName) + .clearPersistence(appName, databaseId) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -78,9 +79,9 @@ public void clearPersistence(String appName, Promise promise) { } @ReactMethod - public void waitForPendingWrites(String appName, Promise promise) { + public void waitForPendingWrites(String appName, String databaseId, Promise promise) { module - .waitForPendingWrites(appName) + .waitForPendingWrites(appName, databaseId) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -92,9 +93,9 @@ public void waitForPendingWrites(String appName, Promise promise) { } @ReactMethod - public void disableNetwork(String appName, Promise promise) { + public void disableNetwork(String appName, String databaseId, Promise promise) { module - .disableNetwork(appName) + .disableNetwork(appName, databaseId) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -106,9 +107,9 @@ public void disableNetwork(String appName, Promise promise) { } @ReactMethod - public void enableNetwork(String appName, Promise promise) { + public void enableNetwork(String appName, String databaseId, Promise promise) { module - .enableNetwork(appName) + .enableNetwork(appName, databaseId) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -120,9 +121,10 @@ public void enableNetwork(String appName, Promise promise) { } @ReactMethod - public void useEmulator(String appName, String host, int port, Promise promise) { + public void useEmulator( + String appName, String databaseId, String host, int port, Promise promise) { module - .useEmulator(appName, host, port) + .useEmulator(appName, databaseId, host, port) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -134,9 +136,10 @@ public void useEmulator(String appName, String host, int port, Promise promise) } @ReactMethod - public void settings(String appName, ReadableMap settings, Promise promise) { + public void settings(String appName, String databaseId, ReadableMap settings, Promise promise) { + String firestoreKey = createFirestoreKey(appName, databaseId); module - .settings(appName, toHashMap(settings)) + .settings(firestoreKey, toHashMap(settings)) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -148,9 +151,9 @@ public void settings(String appName, ReadableMap settings, Promise promise) { } @ReactMethod - public void terminate(String appName, Promise promise) { + public void terminate(String appName, String databaseId, Promise promise) { module - .terminate(appName) + .terminate(appName, databaseId) .addOnCompleteListener( task -> { if (task.isSuccessful()) { diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreQuery.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreQuery.java index 18eaef2e2b..990bc8415a 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreQuery.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreQuery.java @@ -38,10 +38,12 @@ public class ReactNativeFirebaseFirestoreQuery { String appName; + String databaseId; Query query; ReactNativeFirebaseFirestoreQuery( String appName, + String databaseId, Query query, ReadableArray filters, ReadableArray orders, @@ -58,7 +60,7 @@ public Task get(Executor executor, Source source) { executor, () -> { QuerySnapshot querySnapshot = Tasks.await(query.get(source)); - return snapshotToWritableMap(this.appName, "get", querySnapshot, null); + return snapshotToWritableMap(this.appName, this.databaseId, "get", querySnapshot, null); }); } diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java index 58c86233f4..35a9f0727f 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreSerialize.java @@ -98,7 +98,8 @@ public class ReactNativeFirebaseFirestoreSerialize { * @param documentSnapshot DocumentSnapshot * @return WritableMap */ - static WritableMap snapshotToWritableMap(String appName, DocumentSnapshot documentSnapshot) { + static WritableMap snapshotToWritableMap( + String appName, String databaseId, DocumentSnapshot documentSnapshot) { WritableArray metadata = Arguments.createArray(); WritableMap documentMap = Arguments.createMap(); SnapshotMetadata snapshotMetadata = documentSnapshot.getMetadata(); @@ -112,7 +113,7 @@ static WritableMap snapshotToWritableMap(String appName, DocumentSnapshot docume documentMap.putBoolean(KEY_EXISTS, documentSnapshot.exists()); DocumentSnapshot.ServerTimestampBehavior timestampBehavior = - getServerTimestampBehavior(appName); + getServerTimestampBehavior(appName, databaseId); if (documentSnapshot.exists()) { if (documentSnapshot.getData(timestampBehavior) != null) { @@ -132,6 +133,7 @@ static WritableMap snapshotToWritableMap(String appName, DocumentSnapshot docume */ static WritableMap snapshotToWritableMap( String appName, + String databaseId, String source, QuerySnapshot querySnapshot, @Nullable MetadataChanges metadataChanges) { @@ -148,7 +150,8 @@ static WritableMap snapshotToWritableMap( // indicating the data does not include these changes writableMap.putBoolean("excludesMetadataChanges", true); writableMap.putArray( - KEY_CHANGES, documentChangesToWritableArray(appName, documentChangesList, null)); + KEY_CHANGES, + documentChangesToWritableArray(appName, databaseId, documentChangesList, null)); } else { // If listening to metadata changes, get the changes list with document changes array. // To indicate whether a document change was because of metadata change, we check whether @@ -159,7 +162,7 @@ static WritableMap snapshotToWritableMap( writableMap.putArray( KEY_CHANGES, documentChangesToWritableArray( - appName, documentMetadataChangesList, documentChangesList)); + appName, databaseId, documentMetadataChangesList, documentChangesList)); } SnapshotMetadata snapshotMetadata = querySnapshot.getMetadata(); @@ -167,7 +170,7 @@ static WritableMap snapshotToWritableMap( // set documents for (DocumentSnapshot documentSnapshot : documentSnapshots) { - documents.pushMap(snapshotToWritableMap(appName, documentSnapshot)); + documents.pushMap(snapshotToWritableMap(appName, databaseId, documentSnapshot)); } writableMap.putArray(KEY_DOCUMENTS, documents); @@ -188,6 +191,7 @@ static WritableMap snapshotToWritableMap( */ private static WritableArray documentChangesToWritableArray( String appName, + String databaseId, List documentChanges, @Nullable List comparableDocumentChanges) { WritableArray documentChangesWritable = Arguments.createArray(); @@ -212,7 +216,7 @@ private static WritableArray documentChangesToWritableArray( } documentChangesWritable.pushMap( - documentChangeToWritableMap(appName, documentChange, isMetadataChange)); + documentChangeToWritableMap(appName, databaseId, documentChange, isMetadataChange)); } return documentChangesWritable; @@ -225,7 +229,7 @@ private static WritableArray documentChangesToWritableArray( * @return WritableMap */ private static WritableMap documentChangeToWritableMap( - String appName, DocumentChange documentChange, boolean isMetadataChange) { + String appName, String databaseId, DocumentChange documentChange, boolean isMetadataChange) { WritableMap documentChangeMap = Arguments.createMap(); documentChangeMap.putBoolean("isMetadataChange", isMetadataChange); @@ -242,7 +246,8 @@ private static WritableMap documentChangeToWritableMap( } documentChangeMap.putMap( - KEY_DOC_CHANGE_DOCUMENT, snapshotToWritableMap(appName, documentChange.getDocument())); + KEY_DOC_CHANGE_DOCUMENT, + snapshotToWritableMap(appName, databaseId, documentChange.getDocument())); documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex()); documentChangeMap.putInt(KEY_DOC_CHANGE_OLD_INDEX, documentChange.getOldIndex()); diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java index d22efc6f33..fd92061a09 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java @@ -62,7 +62,7 @@ public void onCatalystInstanceDestroy() { @ReactMethod public void transactionGetDocument( - String appName, int transactionId, String path, Promise promise) { + String appName, String databaseId, int transactionId, String path, Promise promise) { ReactNativeFirebaseFirestoreTransactionHandler transactionHandler = transactionHandlers.get(transactionId); @@ -74,12 +74,14 @@ public void transactionGetDocument( return; } - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); Tasks.call( getTransactionalExecutor(), - () -> snapshotToWritableMap(appName, transactionHandler.getDocument(documentReference))) + () -> + snapshotToWritableMap( + appName, databaseId, transactionHandler.getDocument(documentReference))) .addOnCompleteListener( task -> { if (task.isSuccessful()) { @@ -91,7 +93,7 @@ public void transactionGetDocument( } @ReactMethod - public void transactionDispose(String appName, int transactionId) { + public void transactionDispose(String appName, String databaseId, int transactionId) { ReactNativeFirebaseFirestoreTransactionHandler transactionHandler = transactionHandlers.get(transactionId); @@ -103,7 +105,7 @@ public void transactionDispose(String appName, int transactionId) { @ReactMethod public void transactionApplyBuffer( - String appName, int transactionId, ReadableArray commandBuffer) { + String appName, String databaseId, int transactionId, ReadableArray commandBuffer) { ReactNativeFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId); if (handler != null) { @@ -112,12 +114,12 @@ public void transactionApplyBuffer( } @ReactMethod - public void transactionBegin(String appName, int transactionId) { + public void transactionBegin(String appName, String databaseId, int transactionId) { ReactNativeFirebaseFirestoreTransactionHandler transactionHandler = new ReactNativeFirebaseFirestoreTransactionHandler(appName, transactionId); transactionHandlers.put(transactionId, transactionHandler); - FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName); + FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); ReactNativeFirebaseEventEmitter emitter = ReactNativeFirebaseEventEmitter.getSharedInstance(); // Provides its own executor @@ -138,6 +140,7 @@ public void transactionBegin(String appName, int transactionId) { ReactNativeFirebaseFirestoreEvent.TRANSACTION_EVENT_SYNC, eventMap, transactionHandler.getAppName(), + databaseId, transactionHandler.getTransactionId())); }); @@ -227,6 +230,7 @@ public void transactionBegin(String appName, int transactionId) { ReactNativeFirebaseFirestoreEvent.TRANSACTION_EVENT_SYNC, eventMap, transactionHandler.getAppName(), + databaseId, transactionHandler.getTransactionId())); } else { eventMap.putString("type", "error"); @@ -247,6 +251,7 @@ public void transactionBegin(String appName, int transactionId) { ReactNativeFirebaseFirestoreEvent.TRANSACTION_EVENT_SYNC, eventMap, transactionHandler.getAppName(), + databaseId, transactionHandler.getTransactionId())); } }); diff --git a/packages/firestore/e2e/SecondDatabase/second.Transation.e2e.js b/packages/firestore/e2e/SecondDatabase/second.Transation.e2e.js new file mode 100644 index 0000000000..35a18b7998 --- /dev/null +++ b/packages/firestore/e2e/SecondDatabase/second.Transation.e2e.js @@ -0,0 +1,774 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const NO_RULE_COLLECTION = 'no_rules'; + +// This collection is only allowed on the second database +const COLLECTION = 'second-database'; +const SECOND_DATABASE_ID = 'second-rnfb'; + +describe('Second Database', function () { + describe('firestore.Transaction', function () { + describe('v8 compatibility', function () { + let firestore; + + before(function () { + firestore = firebase.app().firestore(SECOND_DATABASE_ID); + }); + + it('should throw if updateFunction is not a Promise', async function () { + try { + await firestore.runTransaction(() => 123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'updateFunction' must return a Promise"); + return Promise.resolve(); + } + }); + + it('should return an instance of FirestoreTransaction', async function () { + await firestore.runTransaction(async transaction => { + transaction.constructor.name.should.eql('FirestoreTransaction'); + return null; + }); + }); + + it('should resolve with user value', async function () { + const expected = Date.now(); + + const value = await firestore.runTransaction(async () => { + return expected; + }); + + value.should.eql(expected); + }); + + it('should reject with user Error', async function () { + const message = `Error: ${Date.now()}`; + + try { + await firestore.runTransaction(async () => { + throw new Error(message); + }); + return Promise.reject(new Error('Did not throw Error.')); + } catch (error) { + error.message.should.eql(message); + return Promise.resolve(); + } + }); + + it('should reject a native error', async function () { + const docRef = firestore.doc(`${NO_RULE_COLLECTION}/foo`); + + try { + await firestore.runTransaction(async t => { + t.set(docRef, { + foo: 'bar', + }); + }); + return Promise.reject(new Error('Did not throw Error.')); + } catch (error) { + error.code.should.eql('firestore/permission-denied'); + return Promise.resolve(); + } + }); + + describe('transaction.get()', function () { + it('should throw if not providing a document reference', async function () { + try { + await firestore.runTransaction(t => { + return t.get(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should get a document and return a DocumentSnapshot', async function () { + const docRef = firestore.doc(`${COLLECTION}/transactions/transaction/get-delete`); + await docRef.set({}); + + await firestore.runTransaction(async t => { + const docSnapshot = await t.get(docRef); + docSnapshot.constructor.name.should.eql('FirestoreDocumentSnapshot'); + docSnapshot.exists.should.eql(true); + docSnapshot.id.should.eql('get-delete'); + + t.delete(docRef); + }); + }); + }); + + describe('transaction.delete()', function () { + it('should throw if not providing a document reference', async function () { + try { + await firestore.runTransaction(async t => { + t.delete(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should delete documents', async function () { + const docRef1 = firestore.doc(`${COLLECTION}/transactions/transaction/delete-delete1`); + await docRef1.set({}); + + const docRef2 = firestore.doc(`${COLLECTION}/transactions/transaction/delete-delete2`); + await docRef2.set({}); + + await firestore.runTransaction(async t => { + t.delete(docRef1); + t.delete(docRef2); + }); + + const snapshot1 = await docRef1.get(); + snapshot1.exists.should.eql(false); + + const snapshot2 = await docRef2.get(); + snapshot2.exists.should.eql(false); + }); + }); + + describe('transaction.update()', function () { + it('should throw if not providing a document reference', async function () { + try { + await firestore.runTransaction(async t => { + t.update(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should throw if update args are invalid', async function () { + const docRef = firestore.doc(`${COLLECTION}/foo`); + + try { + await firestore.runTransaction(async t => { + t.update(docRef, 123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('it must be an object'); + return Promise.resolve(); + } + }); + + it('should update documents', async function () { + const value = Date.now(); + + const docRef1 = firestore.doc(`${COLLECTION}/transactions/transaction/delete-delete1`); + await docRef1.set({ + foo: 'bar', + bar: 'baz', + }); + + const docRef2 = firestore.doc(`${COLLECTION}/transactions/transaction/delete-delete2`); + await docRef2.set({ + foo: 'bar', + bar: 'baz', + }); + + await firestore.runTransaction(async t => { + t.update(docRef1, { + bar: value, + }); + t.update(docRef2, 'bar', value); + }); + + const expected = { + foo: 'bar', + bar: value, + }; + + const snapshot1 = await docRef1.get(); + snapshot1.exists.should.eql(true); + snapshot1.data().should.eql(jet.contextify(expected)); + + const snapshot2 = await docRef2.get(); + snapshot2.exists.should.eql(true); + snapshot2.data().should.eql(jet.contextify(expected)); + }); + }); + + describe('transaction.set()', function () { + it('should throw if not providing a document reference', async function () { + try { + await firestore.runTransaction(async t => { + t.set(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should throw if set data is invalid', async function () { + const docRef = firestore.doc(`${COLLECTION}/foo`); + + try { + await firestore.runTransaction(async t => { + t.set(docRef, 123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'data' must be an object."); + return Promise.resolve(); + } + }); + + it('should throw if set options are invalid', async function () { + const docRef = firestore.doc(`${COLLECTION}/foo`); + + try { + await firestore.runTransaction(async t => { + t.set( + docRef, + {}, + { + merge: true, + mergeFields: [], + }, + ); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "'options' must not contain both 'merge' & 'mergeFields'", + ); + return Promise.resolve(); + } + }); + + it('should set data', async function () { + const docRef = firestore.doc(`${COLLECTION}/transactions/transaction/set`); + await docRef.set({ + foo: 'bar', + }); + const expected = { + foo: 'baz', + }; + + await firestore.runTransaction(async t => { + t.set(docRef, expected); + }); + + const snapshot = await docRef.get(); + snapshot.data().should.eql(jet.contextify(expected)); + }); + + it('should set data with merge', async function () { + const docRef = firestore.doc(`${COLLECTION}/transactions/transaction/set-merge`); + await docRef.set({ + foo: 'bar', + bar: 'baz', + }); + const expected = { + foo: 'bar', + bar: 'foo', + }; + + await firestore.runTransaction(async t => { + t.set( + docRef, + { + bar: 'foo', + }, + { + merge: true, + }, + ); + }); + + const snapshot = await docRef.get(); + snapshot.data().should.eql(jet.contextify(expected)); + }); + + it('should set data with merge fields', async function () { + const docRef = firestore.doc(`${COLLECTION}/transactions/transaction/set-mergefields`); + await docRef.set({ + foo: 'bar', + bar: 'baz', + baz: 'ben', + }); + const expected = { + foo: 'bar', + bar: 'foo', + baz: 'foo', + }; + + await firestore.runTransaction(async t => { + t.set( + docRef, + { + bar: 'foo', + baz: 'foo', + }, + { + mergeFields: ['bar', new firebase.firestore.FieldPath('baz')], + }, + ); + }); + + const snapshot = await docRef.get(); + snapshot.data().should.eql(jet.contextify(expected)); + }); + + it('should roll back any updates that failed', async function () { + const docRef = firestore.doc(`${COLLECTION}/transactions/transaction/rollback`); + + await docRef.set({ + turn: 0, + }); + + const prop1 = 'prop1'; + const prop2 = 'prop2'; + const turn = 0; + const errorMessage = 'turn cannot exceed 1'; + + const createTransaction = prop => { + return firestore.runTransaction(async transaction => { + const doc = await transaction.get(docRef); + const data = doc.data(); + + if (data.turn !== turn) { + throw new Error(errorMessage); + } + + const update = { + turn: turn + 1, + [prop]: 1, + }; + + transaction.update(docRef, update); + }); + }; + + const promises = [createTransaction(prop1), createTransaction(prop2)]; + + try { + await Promise.all(promises); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql(errorMessage); + } + const result = await docRef.get(); + should(result.data()).not.have.properties([prop1, prop2]); + }); + }); + }); + + describe('modular', function () { + let firestore; + + before(function () { + const { getFirestore } = firestoreModular; + firestore = getFirestore(null, SECOND_DATABASE_ID); + }); + + it('should throw if updateFunction is not a Promise', async function () { + const { runTransaction } = firestoreModular; + + try { + await runTransaction(firestore, () => 123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'updateFunction' must return a Promise"); + return Promise.resolve(); + } + }); + + it('should return an instance of FirestoreTransaction', async function () { + const { runTransaction } = firestoreModular; + await runTransaction(firestore, async transaction => { + transaction.constructor.name.should.eql('FirestoreTransaction'); + return null; + }); + }); + + it('should resolve with user value', async function () { + const { runTransaction } = firestoreModular; + const expected = Date.now(); + + const value = await runTransaction(firestore, async () => { + return expected; + }); + + value.should.eql(expected); + }); + + it('should reject with user Error', async function () { + const { runTransaction } = firestoreModular; + const message = `Error: ${Date.now()}`; + + try { + await runTransaction(firestore, async () => { + throw new Error(message); + }); + return Promise.reject(new Error('Did not throw Error.')); + } catch (error) { + error.message.should.eql(message); + return Promise.resolve(); + } + }); + + it('should reject a native error', async function () { + const { runTransaction, doc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${NO_RULE_COLLECTION}/foo`); + + try { + await runTransaction(db, async t => { + t.set(docRef, { + foo: 'bar', + }); + }); + return Promise.reject(new Error('Did not throw Error.')); + } catch (error) { + error.code.should.eql('firestore/permission-denied'); + return Promise.resolve(); + } + }); + + describe('transaction.get()', function () { + it('should throw if not providing a document reference', async function () { + const { runTransaction } = firestoreModular; + try { + await runTransaction(firestore, t => { + return t.get(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should get a document and return a DocumentSnapshot', async function () { + const { runTransaction, doc, setDoc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${COLLECTION}/transactions/transaction/get-delete`); + await setDoc(docRef, {}); + + await runTransaction(db, async t => { + const docSnapshot = await t.get(docRef); + docSnapshot.constructor.name.should.eql('FirestoreDocumentSnapshot'); + docSnapshot.exists.should.eql(true); + docSnapshot.id.should.eql('get-delete'); + + t.delete(docRef); + }); + }); + }); + + describe('transaction.delete()', function () { + it('should throw if not providing a document reference', async function () { + const { runTransaction } = firestoreModular; + try { + await runTransaction(firestore, async t => { + t.delete(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should delete documents', async function () { + const { runTransaction, doc, setDoc, getDoc } = firestoreModular; + const db = firestore; + const docRef1 = doc(db, `${COLLECTION}/transactions/transaction/delete-delete1`); + await setDoc(docRef1, {}); + + const docRef2 = doc(db, `${COLLECTION}/transactions/transaction/delete-delete2`); + await setDoc(docRef2, {}); + + await runTransaction(db, async t => { + t.delete(docRef1); + t.delete(docRef2); + }); + + const snapshot1 = await getDoc(docRef1); + snapshot1.exists.should.eql(false); + + const snapshot2 = await getDoc(docRef2); + snapshot2.exists.should.eql(false); + }); + }); + + describe('transaction.update()', function () { + it('should throw if not providing a document reference', async function () { + const { runTransaction } = firestoreModular; + try { + await runTransaction(firestore, async t => { + t.update(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should throw if update args are invalid', async function () { + const { runTransaction, doc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${COLLECTION}/foo`); + + try { + await runTransaction(db, async t => { + t.update(docRef, 123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('it must be an object'); + return Promise.resolve(); + } + }); + + it('should update documents', async function () { + const { runTransaction, doc, setDoc, getDoc } = firestoreModular; + const db = firestore; + const value = Date.now(); + + const docRef1 = doc(db, `${COLLECTION}/transactions/transaction/delete-delete1`); + await setDoc(docRef1, { + foo: 'bar', + bar: 'baz', + }); + + const docRef2 = doc(db, `${COLLECTION}/transactions/transaction/delete-delete2`); + await setDoc(docRef2, { + foo: 'bar', + bar: 'baz', + }); + + await runTransaction(db, async t => { + t.update(docRef1, { + bar: value, + }); + t.update(docRef2, 'bar', value); + }); + + const expected = { + foo: 'bar', + bar: value, + }; + + const snapshot1 = await getDoc(docRef1); + snapshot1.exists.should.eql(true); + snapshot1.data().should.eql(jet.contextify(expected)); + + const snapshot2 = await getDoc(docRef2); + snapshot2.exists.should.eql(true); + snapshot2.data().should.eql(jet.contextify(expected)); + }); + }); + + describe('transaction.set()', function () { + it('should throw if not providing a document reference', async function () { + const { runTransaction } = firestoreModular; + try { + await runTransaction(firestore, async t => { + t.set(123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'documentRef' expected a DocumentReference"); + return Promise.resolve(); + } + }); + + it('should throw if set data is invalid', async function () { + const { runTransaction, doc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${COLLECTION}/foo`); + + try { + await runTransaction(db, async t => { + t.set(docRef, 123); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'data' must be an object."); + return Promise.resolve(); + } + }); + + it('should throw if set options are invalid', async function () { + const { runTransaction, doc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${COLLECTION}/foo`); + + try { + await runTransaction(db, async t => { + t.set( + docRef, + {}, + { + merge: true, + mergeFields: [], + }, + ); + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "'options' must not contain both 'merge' & 'mergeFields'", + ); + return Promise.resolve(); + } + }); + + it('should set data', async function () { + const { runTransaction, doc, getDoc, setDoc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${COLLECTION}/transactions/transaction/set`); + await setDoc(docRef, { + foo: 'bar', + }); + const expected = { + foo: 'baz', + }; + + await runTransaction(db, async t => { + t.set(docRef, expected); + }); + + const snapshot = await getDoc(docRef); + snapshot.data().should.eql(jet.contextify(expected)); + }); + + it('should set data with merge', async function () { + const { runTransaction, doc, getDoc, setDoc } = firestoreModular; + const db = firestore; + const docRef = doc(db, `${COLLECTION}/transactions/transaction/set-merge`); + await setDoc(docRef, { + foo: 'bar', + bar: 'baz', + }); + const expected = { + foo: 'bar', + bar: 'foo', + }; + + await runTransaction(db, async t => { + t.set( + docRef, + { + bar: 'foo', + }, + { + merge: true, + }, + ); + }); + + const snapshot = await getDoc(docRef); + snapshot.data().should.eql(jet.contextify(expected)); + }); + + it('should set data with merge fields', async function () { + const { runTransaction, doc, getDoc, setDoc } = firestoreModular; + const db = firestore; + + const docRef = doc(db, `${COLLECTION}/transactions/transaction/set-mergefields`); + await setDoc(docRef, { + foo: 'bar', + bar: 'baz', + baz: 'ben', + }); + const expected = { + foo: 'bar', + bar: 'foo', + baz: 'foo', + }; + + await runTransaction(db, async t => { + t.set( + docRef, + { + bar: 'foo', + baz: 'foo', + }, + { + mergeFields: ['bar', new firebase.firestore.FieldPath('baz')], + }, + ); + }); + + const snapshot = await getDoc(docRef); + snapshot.data().should.eql(jet.contextify(expected)); + }); + + it('should roll back any updates that failed', async function () { + const { runTransaction, doc, getDoc, setDoc } = firestoreModular; + const db = firestore; + + const docRef = doc(db, `${COLLECTION}/transactions/transaction/rollback`); + + await setDoc(docRef, { + turn: 0, + }); + + const prop1 = 'prop1'; + const prop2 = 'prop2'; + const turn = 0; + const errorMessage = 'turn cannot exceed 1'; + + const createTransaction = prop => { + return runTransaction(db, async transaction => { + const doc = await transaction.get(docRef); + const data = doc.data(); + + if (data.turn !== turn) { + throw new Error(errorMessage); + } + + const update = { + turn: turn + 1, + [prop]: 1, + }; + + transaction.update(docRef, update); + }); + }; + + const promises = [createTransaction(prop1), createTransaction(prop2)]; + + try { + await Promise.all(promises); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql(errorMessage); + } + const result = await getDoc(docRef); + should(result.data()).not.have.properties([prop1, prop2]); + }); + }); + }); + }); +}); diff --git a/packages/firestore/e2e/SecondDatabase/second.onSnapshot.e2e.js b/packages/firestore/e2e/SecondDatabase/second.onSnapshot.e2e.js new file mode 100644 index 0000000000..2fa8e9fb3c --- /dev/null +++ b/packages/firestore/e2e/SecondDatabase/second.onSnapshot.e2e.js @@ -0,0 +1,696 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +const { wipe } = require('../helpers'); + +const NO_RULE_COLLECTION = 'no_rules'; + +// This collection is only allowed on the second database +const COLLECTION = 'second-database'; +const SECOND_DATABASE_ID = 'second-rnfb'; + +describe('Second Database', function () { + describe('firestore().collection().onSnapshot()', function () { + describe('v8 compatibility', function () { + let firestore; + + before(function () { + firestore = firebase.app().firestore(SECOND_DATABASE_ID); + }); + + beforeEach(async function () { + return await wipe(false, SECOND_DATABASE_ID); + }); + + it('throws if no arguments are provided', function () { + try { + firestore.collection(COLLECTION).onSnapshot(); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('expected at least one argument'); + return Promise.resolve(); + } + }); + + it('returns an unsubscribe function', function () { + const unsub = firestore.collection(`${COLLECTION}/foo/bar1`).onSnapshot(() => {}); + + unsub.should.be.a.Function(); + unsub(); + }); + + it('accepts a single callback function with snapshot', async function () { + if (Platform.other) { + return; + } + const callback = sinon.spy(); + const unsub = firestore.collection(`${COLLECTION}/foo/bar2`).onSnapshot(callback); + + await Utils.spyToBeCalledOnceAsync(callback); + + callback.should.be.calledOnce(); + callback.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(callback.args[0][1], null); + unsub(); + }); + + it('accepts a single callback function with Error', async function () { + if (Platform.other) { + return; + } + const callback = sinon.spy(); + const unsub = firestore.collection(NO_RULE_COLLECTION).onSnapshot(callback); + + await Utils.spyToBeCalledOnceAsync(callback); + + callback.should.be.calledOnce(); + + callback.args[0][1].code.should.containEql('firestore/permission-denied'); + should.equal(callback.args[0][0], null); + unsub(); + }); + + describe('multiple callbacks', function () { + if (Platform.other) { + return; + } + + it('calls onNext when successful', async function () { + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(`${COLLECTION}/foo/bar3`).onSnapshot(onNext, onError); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls onError with Error', async function () { + if (Platform.other) { + return; + } + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(NO_RULE_COLLECTION).onSnapshot(onNext, onError); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + describe('objects of callbacks', function () { + if (Platform.other) { + return; + } + + it('calls next when successful', async function () { + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(`${COLLECTION}/foo/bar4`).onSnapshot({ + next: onNext, + error: onError, + }); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls error with Error', async function () { + if (Platform.other) { + return; + } + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(NO_RULE_COLLECTION).onSnapshot({ + next: onNext, + error: onError, + }); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + describe('SnapshotListenerOptions + callbacks', function () { + if (Platform.other) { + return; + } + + it('calls callback with snapshot when successful', async function () { + const callback = sinon.spy(); + const unsub = firestore.collection(`${COLLECTION}/foo/bar5`).onSnapshot( + { + includeMetadataChanges: false, + }, + callback, + ); + + await Utils.spyToBeCalledOnceAsync(callback); + + callback.should.be.calledOnce(); + callback.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(callback.args[0][1], null); + unsub(); + }); + + it('calls callback with Error', async function () { + const callback = sinon.spy(); + const unsub = firestore.collection(NO_RULE_COLLECTION).onSnapshot( + { + includeMetadataChanges: false, + }, + callback, + ); + + await Utils.spyToBeCalledOnceAsync(callback); + + callback.should.be.calledOnce(); + callback.args[0][1].code.should.containEql('firestore/permission-denied'); + should.equal(callback.args[0][0], null); + unsub(); + }); + + it('calls next with snapshot when successful', async function () { + const onNext = sinon.spy(); + const onError = sinon.spy(); + const colRef = firestore + // Firestore caches aggressively, even if you wipe the emulator, local documents are cached + // between runs, so use random collections to make sure `tests:*:test-reuse` works while iterating + .collection(`${COLLECTION}/${Utils.randString(12, '#aA')}/next-with-snapshot`); + const unsub = colRef.onSnapshot( + { + includeMetadataChanges: false, + }, + onNext, + onError, + ); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls error with Error', async function () { + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(NO_RULE_COLLECTION).onSnapshot( + { + includeMetadataChanges: false, + }, + onNext, + onError, + ); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + describe('SnapshotListenerOptions + object of callbacks', function () { + if (Platform.other) { + return; + } + + it('calls next with snapshot when successful', async function () { + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(`${COLLECTION}/foo/bar7`).onSnapshot( + { + includeMetadataChanges: false, + }, + { + next: onNext, + error: onError, + }, + ); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls error with Error', async function () { + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = firestore.collection(NO_RULE_COLLECTION).onSnapshot( + { + includeMetadataChanges: false, + }, + { + next: onNext, + error: onError, + }, + ); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + it('throws if SnapshotListenerOptions is invalid', function () { + try { + firestore.collection(NO_RULE_COLLECTION).onSnapshot({ + includeMetadataChanges: 123, + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "'options' SnapshotOptions.includeMetadataChanges must be a boolean value", + ); + return Promise.resolve(); + } + }); + + it('throws if next callback is invalid', function () { + try { + firestore.collection(NO_RULE_COLLECTION).onSnapshot({ + next: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'observer.next' or 'onNext' expected a function"); + return Promise.resolve(); + } + }); + + it('throws if error callback is invalid', function () { + try { + firestore.collection(NO_RULE_COLLECTION).onSnapshot({ + error: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'observer.error' or 'onError' expected a function"); + return Promise.resolve(); + } + }); + + // FIXME test disabled due to flakiness in CI E2E tests. + // Registered 4 of 3 expected calls once (!?), 3 of 2 expected calls once. + it('unsubscribes from further updates', async function () { + if (Platform.other) { + return; + } + const callback = sinon.spy(); + + const collection = firestore + // Firestore caches aggressively, even if you wipe the emulator, local documents are cached + // between runs, so use random collections to make sure `tests:*:test-reuse` works while iterating + .collection(`${COLLECTION}/${Utils.randString(12, '#aA')}/unsubscribe-updates`); + + const unsub = collection.onSnapshot(callback); + await Utils.sleep(2000); + await collection.add({}); + await collection.add({}); + unsub(); + await Utils.sleep(2000); + await collection.add({}); + await Utils.sleep(2000); + callback.should.be.callCount(3); + }); + }); + + describe('modular', function () { + let firestore; + + before(function () { + const { getFirestore } = firestoreModular; + firestore = getFirestore(null, SECOND_DATABASE_ID); + }); + + beforeEach(async function () { + return await wipe(false, SECOND_DATABASE_ID); + }); + + it('throws if no arguments are provided', function () { + const { collection, onSnapshot } = firestoreModular; + try { + onSnapshot(collection(firestore, COLLECTION)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('expected at least one argument'); + return Promise.resolve(); + } + }); + + it('returns an unsubscribe function', function () { + const { collection, onSnapshot } = firestoreModular; + const unsub = onSnapshot(collection(firestore, `${COLLECTION}/foo/bar1`), () => {}); + + unsub.should.be.a.Function(); + unsub(); + }); + + it('accepts a single callback function with snapshot', async function () { + if (Platform.other) { + return; + } + const { collection, onSnapshot } = firestoreModular; + const callback = sinon.spy(); + const unsub = onSnapshot(collection(firestore, `${COLLECTION}/foo/bar2`), callback); + + await Utils.spyToBeCalledOnceAsync(callback); + + callback.should.be.calledOnce(); + callback.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(callback.args[0][1], null); + unsub(); + }); + + describe('multiple callbacks', function () { + if (Platform.other) { + return; + } + + it('calls onNext when successful', async function () { + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot( + collection(firestore, `${COLLECTION}/foo/bar3`), + onNext, + onError, + ); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls onError with Error', async function () { + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot(collection(firestore, NO_RULE_COLLECTION), onNext, onError); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + describe('objects of callbacks', function () { + if (Platform.other) { + return; + } + + it('calls next when successful', async function () { + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot(collection(firestore, `${COLLECTION}/foo/bar4`), { + next: onNext, + error: onError, + }); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls error with Error', async function () { + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot(collection(firestore, NO_RULE_COLLECTION), { + next: onNext, + error: onError, + }); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + describe('SnapshotListenerOptions + callbacks', function () { + if (Platform.other) { + return; + } + + it('calls callback with snapshot when successful', async function () { + const { collection, onSnapshot } = firestoreModular; + const callback = sinon.spy(); + const unsub = onSnapshot( + collection(firestore, `${COLLECTION}/foo/bar5`), + { + includeMetadataChanges: false, + }, + callback, + ); + + await Utils.spyToBeCalledOnceAsync(callback); + + callback.should.be.calledOnce(); + callback.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(callback.args[0][1], null); + unsub(); + }); + + it('calls next with snapshot when successful', async function () { + if (Platform.other) { + return; + } + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const colRef = collection( + firestore, + // Firestore caches aggressively, even if you wipe the emulator, local documents are cached + // between runs, so use random collections to make sure `tests:*:test-reuse` works while iterating + `${COLLECTION}/${Utils.randString(12, '#aA')}/next-with-snapshot`, + ); + const unsub = onSnapshot( + colRef, + { + includeMetadataChanges: false, + }, + onNext, + onError, + ); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls error with Error', async function () { + if (Platform.other) { + return; + } + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot( + collection(firestore, NO_RULE_COLLECTION), + { + includeMetadataChanges: false, + }, + onNext, + onError, + ); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + describe('SnapshotListenerOptions + object of callbacks', function () { + if (Platform.other) { + return; + } + + it('calls next with snapshot when successful', async function () { + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot( + collection(firestore, `${COLLECTION}/foo/bar7`), + { + includeMetadataChanges: false, + }, + { + next: onNext, + error: onError, + }, + ); + + await Utils.spyToBeCalledOnceAsync(onNext); + + onNext.should.be.calledOnce(); + onError.should.be.callCount(0); + onNext.args[0][0].constructor.name.should.eql('FirestoreQuerySnapshot'); + should.equal(onNext.args[0][1], undefined); + unsub(); + }); + + it('calls error with Error', async function () { + const { collection, onSnapshot } = firestoreModular; + const onNext = sinon.spy(); + const onError = sinon.spy(); + const unsub = onSnapshot( + collection(firestore, NO_RULE_COLLECTION), + { + includeMetadataChanges: false, + }, + { + next: onNext, + error: onError, + }, + ); + + await Utils.spyToBeCalledOnceAsync(onError); + + onError.should.be.calledOnce(); + onNext.should.be.callCount(0); + onError.args[0][0].code.should.containEql('firestore/permission-denied'); + should.equal(onError.args[0][1], undefined); + unsub(); + }); + }); + + it('throws if SnapshotListenerOptions is invalid', function () { + const { collection, onSnapshot } = firestoreModular; + try { + onSnapshot(collection(firestore, NO_RULE_COLLECTION), { + includeMetadataChanges: 123, + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "'options' SnapshotOptions.includeMetadataChanges must be a boolean value", + ); + return Promise.resolve(); + } + }); + + it('throws if next callback is invalid', function () { + const { collection, onSnapshot } = firestoreModular; + try { + onSnapshot(collection(firestore, NO_RULE_COLLECTION), { + next: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'observer.next' or 'onNext' expected a function"); + return Promise.resolve(); + } + }); + + it('throws if error callback is invalid', function () { + const { collection, onSnapshot } = firestoreModular; + try { + onSnapshot(collection(firestore, NO_RULE_COLLECTION), { + error: 'foo', + }); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'observer.error' or 'onError' expected a function"); + return Promise.resolve(); + } + }); + + // FIXME test disabled due to flakiness in CI E2E tests. + // Registered 4 of 3 expected calls once (!?), 3 of 2 expected calls once. + it('unsubscribes from further updates', async function () { + if (Platform.other) { + return; + } + const { collection, onSnapshot, addDoc } = firestoreModular; + const callback = sinon.spy(); + + const collectionRef = collection( + firestore, + // Firestore caches aggressively, even if you wipe the emulator, local documents are cached + // between runs, so use random collections to make sure `tests:*:test-reuse` works while iterating + `${COLLECTION}/${Utils.randString(12, '#aA')}/unsubscribe-updates`, + ); + + const unsub = onSnapshot(collectionRef, callback); + await Utils.sleep(2000); + await addDoc(collectionRef, {}); + await addDoc(collectionRef, {}); + unsub(); + await Utils.sleep(2000); + await addDoc(collectionRef, {}); + await Utils.sleep(2000); + callback.should.be.callCount(3); + }); + }); + }); +}); diff --git a/packages/firestore/e2e/SecondDatabase/second.where.e2e.js b/packages/firestore/e2e/SecondDatabase/second.where.e2e.js new file mode 100644 index 0000000000..0000371d42 --- /dev/null +++ b/packages/firestore/e2e/SecondDatabase/second.where.e2e.js @@ -0,0 +1,1095 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +const { wipe } = require('../helpers'); + +// This collection is only allowed on the second database +const COLLECTION = 'second-database'; +const SECOND_DATABASE_ID = 'second-rnfb'; + +describe('Second Database', function () { + describe('firestore().collection().where()', function () { + describe('v8 compatibility', function () { + let firestore; + + before(function () { + firestore = firebase.app().firestore(SECOND_DATABASE_ID); + }); + + beforeEach(async function () { + return await wipe(false, SECOND_DATABASE_ID); + }); + + it('throws if fieldPath is invalid', function () { + try { + firestore.collection(COLLECTION).where(123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'must be a string, instance of FieldPath or instance of Filter', + ); + return Promise.resolve(); + } + }); + + it('throws if fieldPath string is invalid', function () { + try { + firestore.collection(COLLECTION).where('.foo.bar'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'fieldPath' Invalid field path"); + return Promise.resolve(); + } + }); + + it('throws if operator string is invalid', function () { + try { + firestore.collection(COLLECTION).where('foo.bar', '!'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'opStr' is invalid"); + return Promise.resolve(); + } + }); + + it('throws if query contains multiple array-contains', function () { + try { + firestore + .collection(COLLECTION) + .where('foo.bar', 'array-contains', 123) + .where('foo.bar', 'array-contains', 123); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('Queries only support a single array-contains filter'); + return Promise.resolve(); + } + }); + + it('throws if value is not defined', function () { + try { + firestore.collection(COLLECTION).where('foo.bar', 'array-contains'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' argument expected"); + return Promise.resolve(); + } + }); + + it('throws if null value and no equal operator', function () { + try { + firestore.collection(COLLECTION).where('foo.bar', 'array-contains', null); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('You can only perform equals comparisons on null'); + return Promise.resolve(); + } + }); + + it('allows null to be used with equal operator', function () { + firestore.collection(COLLECTION).where('foo.bar', '==', null); + }); + + it('allows null to be used with not equal operator', function () { + firestore.collection(COLLECTION).where('foo.bar', '!=', null); + }); + + it('allows inequality on the same path', function () { + firestore + .collection(COLLECTION) + .where('foo.bar', '>', 123) + .where(new firebase.firestore.FieldPath('foo', 'bar'), '>', 1234); + }); + + it('throws if in query with no array value', function () { + try { + firestore.collection(COLLECTION).where('foo.bar', 'in', '123'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('A non-empty array is required'); + return Promise.resolve(); + } + }); + + it('throws if array-contains-any query with no array value', function () { + try { + firestore.collection(COLLECTION).where('foo.bar', 'array-contains-any', '123'); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('A non-empty array is required'); + return Promise.resolve(); + } + }); + + it('throws if in query array length is greater than 30', function () { + try { + const queryArray = Array.from({ length: 31 }, (_, i) => i + 1); + + firestore.collection(COLLECTION).where('foo.bar', 'in', queryArray); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('maximum of 30 elements in the value'); + return Promise.resolve(); + } + }); + + it('throws if query has multiple array-contains-any filter', function () { + try { + firestore + .collection(COLLECTION) + .where('foo.bar', 'array-contains-any', [1]) + .where('foo.bar', 'array-contains-any', [2]); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "You cannot use more than one 'array-contains-any' filter", + ); + return Promise.resolve(); + } + }); + + /* Queries */ + + it('returns with where equal filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/equal`); + + const search = Date.now(); + await Promise.all([ + colRef.add({ foo: search }), + colRef.add({ foo: search }), + colRef.add({ foo: search + 1234 }), + ]); + + const snapshot = await colRef.where('foo', '==', search).get(); + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.eql(search); + }); + }); + + it('returns with where greater than filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/greater`); + + const search = Date.now(); + await Promise.all([ + colRef.add({ foo: search - 1234 }), + colRef.add({ foo: search }), + colRef.add({ foo: search + 1234 }), + colRef.add({ foo: search + 1234 }), + ]); + + const snapshot = await colRef.where('foo', '>', search).get(); + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.eql(search + 1234); + }); + }); + + it('returns with where greater than or equal filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/greaterequal`); + + const search = Date.now(); + await Promise.all([ + colRef.add({ foo: search - 1234 }), + colRef.add({ foo: search }), + colRef.add({ foo: search + 1234 }), + colRef.add({ foo: search + 1234 }), + ]); + + const snapshot = await colRef.where('foo', '>=', search).get(); + + snapshot.size.should.eql(3); + snapshot.forEach(s => { + s.data().foo.should.be.aboveOrEqual(search); + }); + }); + + it('returns with where less than filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/less`); + + const search = -Date.now(); + await Promise.all([ + colRef.add({ foo: search + -1234 }), + colRef.add({ foo: search + -1234 }), + colRef.add({ foo: search }), + ]); + + const snapshot = await colRef.where('foo', '<', search).get(); + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.be.below(search); + }); + }); + + it('returns with where less than or equal filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/lessequal`); + + const search = -Date.now(); + await Promise.all([ + colRef.add({ foo: search + -1234 }), + colRef.add({ foo: search + -1234 }), + colRef.add({ foo: search }), + colRef.add({ foo: search + 1234 }), + ]); + + const snapshot = await colRef.where('foo', '<=', search).get(); + + snapshot.size.should.eql(3); + snapshot.forEach(s => { + s.data().foo.should.be.belowOrEqual(search); + }); + }); + + it('returns when combining greater than and lesser than on the same nested field', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/greaterandless`); + + await Promise.all([ + colRef.doc('doc1').set({ foo: { bar: 1 } }), + colRef.doc('doc2').set({ foo: { bar: 2 } }), + colRef.doc('doc3').set({ foo: { bar: 3 } }), + ]); + + const snapshot = await colRef + .where('foo.bar', '>', 1) + .where('foo.bar', '<', 3) + .orderBy('foo.bar') + .get(); + + snapshot.size.should.eql(1); + }); + + it('returns when combining greater than and lesser than on the same nested field using FieldPath', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/greaterandless`); + + await Promise.all([ + colRef.doc('doc1').set({ foo: { bar: 1 } }), + colRef.doc('doc2').set({ foo: { bar: 2 } }), + colRef.doc('doc3').set({ foo: { bar: 3 } }), + ]); + + const snapshot = await colRef + .where(new firebase.firestore.FieldPath('foo', 'bar'), '>', 1) + .where(new firebase.firestore.FieldPath('foo', 'bar'), '<', 3) + .orderBy(new firebase.firestore.FieldPath('foo', 'bar')) + .get(); + + snapshot.size.should.eql(1); + }); + + it('returns with where array-contains filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/array-contains`); + + const match = Date.now(); + await Promise.all([ + colRef.add({ foo: [1, '2', match] }), + colRef.add({ foo: [1, '2', match.toString()] }), + colRef.add({ foo: [1, '2', match.toString()] }), + ]); + + const snapshot = await colRef.where('foo', 'array-contains', match.toString()).get(); + const expected = [1, '2', match.toString()]; + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.eql(jet.contextify(expected)); + }); + }); + + it('returns with in filter', async function () { + const colRef = firestore.collection(`${COLLECTION}/filter/in${Date.now() + ''}`); + + await Promise.all([ + colRef.add({ status: 'Ordered' }), + colRef.add({ status: 'Ready to Ship' }), + colRef.add({ status: 'Ready to Ship' }), + colRef.add({ status: 'Incomplete' }), + ]); + + const expect = ['Ready to Ship', 'Ordered']; + const snapshot = await colRef.where('status', 'in', expect).get(); + snapshot.size.should.eql(3); + + snapshot.forEach(s => { + s.data().status.should.equalOneOf(...expect); + }); + }); + + it('returns with array-contains-any filter', async function () { + const colRef = firestore.collection( + `${COLLECTION}/filter/array-contains-any${Date.now() + ''}`, + ); + + await Promise.all([ + colRef.add({ category: ['Appliances', 'Housewares', 'Cooking'] }), + colRef.add({ category: ['Appliances', 'Electronics', 'Nursery'] }), + colRef.add({ category: ['Audio/Video', 'Electronics'] }), + colRef.add({ category: ['Beauty'] }), + ]); + + const expect = ['Appliances', 'Electronics']; + const snapshot = await colRef.where('category', 'array-contains-any', expect).get(); + snapshot.size.should.eql(3); // 2nd record should only be returned once + }); + + it('returns with a FieldPath', async function () { + const colRef = firestore.collection( + `${COLLECTION}/filter/where-fieldpath${Date.now() + ''}`, + ); + const fieldPath = new firebase.firestore.FieldPath('map', 'foo.bar@gmail.com'); + + await colRef.add({ + map: { + 'foo.bar@gmail.com': true, + }, + }); + await colRef.add({ + map: { + 'bar.foo@gmail.com': true, + }, + }); + + const snapshot = await colRef.where(fieldPath, '==', true).get(); + snapshot.size.should.eql(1); // 2nd record should only be returned once + const data = snapshot.docs[0].data(); + should.equal(data.map['foo.bar@gmail.com'], true); + }); + + it('should throw an error if you use a FieldPath on a filter in conjunction with an orderBy() parameter that is not FieldPath', async function () { + try { + firestore + .collection(COLLECTION) + .where(firebase.firestore.FieldPath.documentId(), 'in', ['document-id']) + .orderBy('differentOrderBy', 'desc'); + + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'FirestoreFieldPath' cannot be used in conjunction"); + return Promise.resolve(); + } + }); + + it('should correctly query integer values with in operator', async function () { + const ref = firestore.collection(`${COLLECTION}/filter/int-in${Date.now() + ''}`); + + await ref.add({ status: 1 }); + + const items = []; + await ref + .where('status', 'in', [1, 2]) + .get() + .then($ => $.forEach(doc => items.push(doc.data()))); + + items.length.should.equal(1); + }); + + it('should correctly query integer values with array-contains operator', async function () { + const ref = firestore.collection( + `${COLLECTION}/filter/int-array-contains${Date.now() + ''}`, + ); + + await ref.add({ status: [1, 2, 3] }); + + const items = []; + await ref + .where('status', 'array-contains', 2) + .get() + .then($ => $.forEach(doc => items.push(doc.data()))); + + items.length.should.equal(1); + }); + + it("should correctly retrieve data when using 'not-in' operator", async function () { + const ref = firestore.collection(`${COLLECTION}/filter/not-in${Date.now() + ''}`); + + await Promise.all([ref.add({ notIn: 'here' }), ref.add({ notIn: 'now' })]); + + const result = await ref.where('notIn', 'not-in', ['here', 'there', 'everywhere']).get(); + should(result.docs.length).equal(1); + should(result.docs[0].data().notIn).equal('now'); + }); + + it("should throw error when using 'not-in' operator twice", async function () { + const ref = firestore.collection(COLLECTION); + + try { + ref.where('test', 'not-in', [1]).where('test', 'not-in', [2]); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You cannot use more than one 'not-in' filter."); + return Promise.resolve(); + } + }); + + it("should throw error when combining 'not-in' operator with '!=' operator", async function () { + const ref = firestore.collection(COLLECTION); + + try { + ref.where('test', '!=', 1).where('test', 'not-in', [1]); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "You cannot use 'not-in' filters with '!=' inequality filters", + ); + return Promise.resolve(); + } + }); + + it("should throw error when combining 'not-in' operator with 'in' operator", async function () { + const ref = firestore.collection(COLLECTION); + + try { + ref.where('test', 'in', [2]).where('test', 'not-in', [1]); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You cannot use 'not-in' filters with 'in' filters."); + return Promise.resolve(); + } + }); + + it("should throw error when combining 'not-in' operator with 'array-contains-any' operator", async function () { + const ref = firestore.collection(COLLECTION); + + try { + ref.where('test', 'array-contains-any', [2]).where('test', 'not-in', [1]); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "You cannot use 'not-in' filters with 'array-contains-any' filters.", + ); + return Promise.resolve(); + } + }); + + it("should throw error when 'not-in' filter has a list of more than 10 items", async function () { + const ref = firestore.collection(COLLECTION); + const queryArray = Array.from({ length: 31 }, (_, i) => i + 1); + + try { + ref.where('test', 'not-in', queryArray); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'filters support a maximum of 30 elements in the value array.', + ); + return Promise.resolve(); + } + }); + + it("should correctly retrieve data when using '!=' operator", async function () { + const ref = firestore.collection(`${COLLECTION}/filter/bang-equals${Date.now() + ''}`); + + await Promise.all([ref.add({ notEqual: 'here' }), ref.add({ notEqual: 'now' })]); + + const result = await ref.where('notEqual', '!=', 'here').get(); + + should(result.docs.length).equal(1); + should(result.docs[0].data().notEqual).equal('now'); + }); + + it("should throw error when using '!=' operator twice ", async function () { + const ref = firestore.collection(COLLECTION); + + try { + ref.where('test', '!=', 1).where('test', '!=', 2); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You cannot use more than one '!=' inequality filter."); + return Promise.resolve(); + } + }); + + it('should handle where clause after sort by', async function () { + const ref = firestore.collection(`${COLLECTION}/filter/sort-by-where${Date.now() + ''}`); + + await ref.add({ status: 1 }); + await ref.add({ status: 2 }); + await ref.add({ status: 3 }); + + const items = []; + await ref + .orderBy('status', 'desc') + .where('status', '<=', 2) + .get() + .then($ => $.forEach(doc => items.push(doc.data()))); + + items.length.should.equal(2); + items[0].status.should.equal(2); + items[1].status.should.equal(1); + }); + }); + + describe('modular', function () { + let firestore; + + before(function () { + const { getFirestore } = firestoreModular; + firestore = getFirestore(null, SECOND_DATABASE_ID); + }); + + beforeEach(async function () { + return await wipe(false, SECOND_DATABASE_ID); + }); + + it('throws if fieldPath is invalid', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where(123)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'must be a string, instance of FieldPath or instance of Filter', + ); + return Promise.resolve(); + } + }); + + it('throws if fieldPath string is invalid', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where('.foo.bar')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'fieldPath' Invalid field path"); + return Promise.resolve(); + } + }); + + it('throws if operator string is invalid', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where('foo.bar', '!')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'opStr' is invalid"); + return Promise.resolve(); + } + }); + + it('throws if query contains multiple array-contains', function () { + const { collection, query, where } = firestoreModular; + try { + query( + collection(firestore, COLLECTION), + where('foo.bar', 'array-contains', 123), + where('foo.bar', 'array-contains', 123), + ); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('Queries only support a single array-contains filter'); + return Promise.resolve(); + } + }); + + it('throws if value is not defined', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where('foo.bar', 'array-contains')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'value' argument expected"); + return Promise.resolve(); + } + }); + + it('throws if null value and no equal operator', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where('foo.bar', 'array-contains', null)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('You can only perform equals comparisons on null'); + return Promise.resolve(); + } + }); + + it('allows null to be used with equal operator', function () { + const { collection, query, where } = firestoreModular; + query(collection(firestore, COLLECTION), where('foo.bar', '==', null)); + }); + + it('allows null to be used with not equal operator', function () { + const { collection, query, where } = firestoreModular; + query(collection(firestore, COLLECTION), where('foo.bar', '!=', null)); + }); + + it('allows inequality on the same path', function () { + const { collection, query, where, FieldPath } = firestoreModular; + query( + collection(firestore, COLLECTION), + where('foo.bar', '>', 123), + where(new FieldPath('foo', 'bar'), '>', 1234), + ); + }); + + it('throws if in query with no array value', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where('foo.bar', 'in', '123')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('A non-empty array is required'); + return Promise.resolve(); + } + }); + + it('throws if array-contains-any query with no array value', function () { + const { collection, query, where } = firestoreModular; + try { + query(collection(firestore, COLLECTION), where('foo.bar', 'array-contains-any', '123')); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('A non-empty array is required'); + return Promise.resolve(); + } + }); + + it('throws if in query array length is greater than 30', function () { + const { collection, query, where } = firestoreModular; + const queryArray = Array.from({ length: 31 }, (_, i) => i + 1); + + try { + query(collection(firestore, COLLECTION), where('foo.bar', 'in', queryArray)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql('maximum of 30 elements in the value'); + return Promise.resolve(); + } + }); + + it('throws if query has multiple array-contains-any filter', function () { + const { collection, query, where } = firestoreModular; + try { + query( + collection(firestore, COLLECTION), + where('foo.bar', 'array-contains-any', [1]), + where('foo.bar', 'array-contains-any', [2]), + ); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "You cannot use more than one 'array-contains-any' filter", + ); + return Promise.resolve(); + } + }); + + /* Queries */ + + it('returns with where equal filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/equal`); + + const search = Date.now(); + await Promise.all([ + addDoc(colRef, { foo: search }), + addDoc(colRef, { foo: search }), + addDoc(colRef, { foo: search + 1234 }), + ]); + + const snapshot = await getDocs(query(colRef, where('foo', '==', search))); + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.eql(search); + }); + }); + + it('returns with where greater than filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/greater`); + + const search = Date.now(); + await Promise.all([ + addDoc(colRef, { foo: search - 1234 }), + addDoc(colRef, { foo: search }), + addDoc(colRef, { foo: search + 1234 }), + addDoc(colRef, { foo: search + 1234 }), + ]); + + const snapshot = await getDocs(query(colRef, where('foo', '>', search))); + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.eql(search + 1234); + }); + }); + + it('returns with where greater than or equal filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/greaterequal`); + + const search = Date.now(); + await Promise.all([ + addDoc(colRef, { foo: search - 1234 }), + addDoc(colRef, { foo: search }), + addDoc(colRef, { foo: search + 1234 }), + addDoc(colRef, { foo: search + 1234 }), + ]); + + const snapshot = await getDocs(query(colRef, where('foo', '>=', search))); + + snapshot.size.should.eql(3); + snapshot.forEach(s => { + s.data().foo.should.be.aboveOrEqual(search); + }); + }); + + it('returns with where less than filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/less`); + + const search = -Date.now(); + await Promise.all([ + addDoc(colRef, { foo: search + -1234 }), + addDoc(colRef, { foo: search + -1234 }), + addDoc(colRef, { foo: search }), + ]); + + const snapshot = await getDocs(query(colRef, where('foo', '<', search))); + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.be.below(search); + }); + }); + + it('returns with where less than or equal filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/lessequal`); + + const search = -Date.now(); + await Promise.all([ + addDoc(colRef, { foo: search + -1234 }), + addDoc(colRef, { foo: search + -1234 }), + addDoc(colRef, { foo: search }), + addDoc(colRef, { foo: search + 1234 }), + ]); + + const snapshot = await getDocs(query(colRef, where('foo', '<=', search))); + + snapshot.size.should.eql(3); + snapshot.forEach(s => { + s.data().foo.should.be.belowOrEqual(search); + }); + }); + + it('returns when combining greater than and lesser than on the same nested field', async function () { + const { collection, doc, setDoc, query, where, orderBy, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/greaterandless`); + + await Promise.all([ + setDoc(doc(colRef, 'doc1'), { foo: { bar: 1 } }), + setDoc(doc(colRef, 'doc2'), { foo: { bar: 2 } }), + setDoc(doc(colRef, 'doc3'), { foo: { bar: 3 } }), + ]); + + const snapshot = await getDocs( + query(colRef, where('foo.bar', '>', 1), where('foo.bar', '<', 3), orderBy('foo.bar')), + ); + + snapshot.size.should.eql(1); + }); + + it('returns when combining greater than and lesser than on the same nested field using FieldPath', async function () { + const { collection, doc, setDoc, query, where, getDocs, orderBy, FieldPath } = + firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/greaterandless`); + + await Promise.all([ + setDoc(doc(colRef, 'doc1'), { foo: { bar: 1 } }), + setDoc(doc(colRef, 'doc2'), { foo: { bar: 2 } }), + setDoc(doc(colRef, 'doc3'), { foo: { bar: 3 } }), + ]); + + const snapshot = await getDocs( + query( + colRef, + where(new FieldPath('foo', 'bar'), '>', 1), + where(new FieldPath('foo', 'bar'), '<', 3), + orderBy(new FieldPath('foo', 'bar')), + ), + ); + + snapshot.size.should.eql(1); + }); + + it('returns with where array-contains filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/array-contains`); + + const match = Date.now(); + await Promise.all([ + addDoc(colRef, { foo: [1, '2', match] }), + addDoc(colRef, { foo: [1, '2', match.toString()] }), + addDoc(colRef, { foo: [1, '2', match.toString()] }), + ]); + + const snapshot = await getDocs( + query(colRef, where('foo', 'array-contains', match.toString())), + ); + const expected = [1, '2', match.toString()]; + + snapshot.size.should.eql(2); + snapshot.forEach(s => { + s.data().foo.should.eql(jet.contextify(expected)); + }); + }); + + it('returns with in filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection(firestore, `${COLLECTION}/filter/in${Date.now() + ''}`); + + await Promise.all([ + addDoc(colRef, { status: 'Ordered' }), + addDoc(colRef, { status: 'Ready to Ship' }), + addDoc(colRef, { status: 'Ready to Ship' }), + addDoc(colRef, { status: 'Incomplete' }), + ]); + + const expect = ['Ready to Ship', 'Ordered']; + const snapshot = await getDocs(query(colRef, where('status', 'in', expect))); + snapshot.size.should.eql(3); + + snapshot.forEach(s => { + s.data().status.should.equalOneOf(...expect); + }); + }); + + it('returns with array-contains-any filter', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const colRef = collection( + firestore, + `${COLLECTION}/filter/array-contains-any${Date.now() + ''}`, + ); + + await Promise.all([ + addDoc(colRef, { category: ['Appliances', 'Housewares', 'Cooking'] }), + addDoc(colRef, { category: ['Appliances', 'Electronics', 'Nursery'] }), + addDoc(colRef, { category: ['Audio/Video', 'Electronics'] }), + addDoc(colRef, { category: ['Beauty'] }), + ]); + + const expect = ['Appliances', 'Electronics']; + const snapshot = await getDocs( + query(colRef, where('category', 'array-contains-any', expect)), + ); + snapshot.size.should.eql(3); // 2nd record should only be returned once + }); + + it('returns with a FieldPath', async function () { + const { collection, addDoc, query, where, getDocs, FieldPath } = firestoreModular; + const colRef = collection( + firestore, + `${COLLECTION}/filter/where-fieldpath${Date.now() + ''}`, + ); + const fieldPath = new FieldPath('map', 'foo.bar@gmail.com'); + + await addDoc(colRef, { + map: { + 'foo.bar@gmail.com': true, + }, + }); + await addDoc(colRef, { + map: { + 'bar.foo@gmail.com': true, + }, + }); + + const snapshot = await getDocs(query(colRef, where(fieldPath, '==', true))); + snapshot.size.should.eql(1); // 2nd record should only be returned once + const data = snapshot.docs[0].data(); + should.equal(data.map['foo.bar@gmail.com'], true); + }); + + it('should throw an error if you use a FieldPath on a filter in conjunction with an orderBy() parameter that is not FieldPath', async function () { + const { collection, query, where, orderBy, FieldPath } = firestoreModular; + try { + query( + collection(firestore, COLLECTION), + where(FieldPath.documentId(), 'in', ['document-id']), + orderBy('differentOrderBy', 'desc'), + ); + + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("'FirestoreFieldPath' cannot be used in conjunction"); + return Promise.resolve(); + } + }); + + it('should correctly query integer values with in operator', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const ref = collection(firestore, `${COLLECTION}/filter/int-in${Date.now() + ''}`); + + await addDoc(ref, { status: 1 }); + + const items = []; + await getDocs(query(ref, where('status', 'in', [1, 2]))).then($ => + $.forEach(doc => items.push(doc.data())), + ); + + items.length.should.equal(1); + }); + + it('should correctly query integer values with array-contains operator', async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const ref = collection( + firestore, + `${COLLECTION}/filter/int-array-contains${Date.now() + ''}`, + ); + + await addDoc(ref, { status: [1, 2, 3] }); + + const items = []; + await getDocs(query(ref, where('status', 'array-contains', 2))).then($ => + $.forEach(doc => items.push(doc.data())), + ); + + items.length.should.equal(1); + }); + + it("should correctly retrieve data when using 'not-in' operator", async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const ref = collection(firestore, `${COLLECTION}/filter/not-in${Date.now() + ''}`); + + await Promise.all([addDoc(ref, { notIn: 'here' }), addDoc(ref, { notIn: 'now' })]); + + const result = await getDocs( + query(ref, where('notIn', 'not-in', ['here', 'there', 'everywhere'])), + ); + should(result.docs.length).equal(1); + should(result.docs[0].data().notIn).equal('now'); + }); + + it("should throw error when using 'not-in' operator twice", async function () { + const { collection, query, where } = firestoreModular; + const ref = collection(firestore, COLLECTION); + + try { + query(ref, where('test', 'not-in', [1]), where('test', 'not-in', [2])); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You cannot use more than one 'not-in' filter."); + return Promise.resolve(); + } + }); + + it("should throw error when combining 'not-in' operator with '!=' operator", async function () { + const { collection, query, where } = firestoreModular; + const ref = collection(firestore, COLLECTION); + + try { + query(ref, where('test', 'not-in', [1]), where('test', '!=', 1)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "You cannot use 'not-in' filters with '!=' inequality filters", + ); + return Promise.resolve(); + } + }); + + it("should throw error when combining 'not-in' operator with 'in' operator", async function () { + const { collection, query, where } = firestoreModular; + const ref = collection(firestore, COLLECTION); + + try { + query(ref, where('test', 'in', [2]), where('test', 'not-in', [1])); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You cannot use 'not-in' filters with 'in' filters."); + return Promise.resolve(); + } + }); + + it("should throw error when combining 'not-in' operator with 'array-contains-any' operator", async function () { + const { collection, query, where } = firestoreModular; + const ref = collection(firestore, COLLECTION); + + try { + query(ref, where('test', 'array-contains-any', [2]), where('test', 'not-in', [1])); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + "You cannot use 'not-in' filters with 'array-contains-any' filters.", + ); + return Promise.resolve(); + } + }); + + it("should throw error when 'not-in' filter has a list of more than 10 items", async function () { + const { collection, query, where } = firestoreModular; + const ref = collection(firestore, COLLECTION); + const queryArray = Array.from({ length: 31 }, (_, i) => i + 1); + + try { + query(ref, where('test', 'not-in', queryArray)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql( + 'filters support a maximum of 30 elements in the value array.', + ); + return Promise.resolve(); + } + }); + + it("should correctly retrieve data when using '!=' operator", async function () { + const { collection, addDoc, query, where, getDocs } = firestoreModular; + const ref = collection(firestore, `${COLLECTION}/filter/bang-equals${Date.now() + ''}`); + + await Promise.all([addDoc(ref, { notEqual: 'here' }), addDoc(ref, { notEqual: 'now' })]); + + const result = await getDocs(query(ref, where('notEqual', '!=', 'here'))); + + should(result.docs.length).equal(1); + should(result.docs[0].data().notEqual).equal('now'); + }); + + it("should throw error when using '!=' operator twice ", async function () { + const { collection, query, where } = firestoreModular; + const ref = collection(firestore, COLLECTION); + + try { + query(ref, where('test', '!=', 1), where('test', '!=', 2)); + return Promise.reject(new Error('Did not throw an Error.')); + } catch (error) { + error.message.should.containEql("You cannot use more than one '!=' inequality filter."); + return Promise.resolve(); + } + }); + + it('should handle where clause after sort by', async function () { + const { collection, addDoc, query, where, orderBy, getDocs } = firestoreModular; + const ref = collection(firestore, `${COLLECTION}/filter/sort-by-where${Date.now() + ''}`); + + await addDoc(ref, { status: 1 }); + await addDoc(ref, { status: 2 }); + await addDoc(ref, { status: 3 }); + + const items = []; + await getDocs(query(ref, orderBy('status', 'desc'), where('status', '<=', 2))).then($ => + $.forEach(doc => items.push(doc.data())), + ); + + items.length.should.equal(2); + items[0].status.should.equal(2); + items[1].status.should.equal(1); + }); + }); + }); +}); diff --git a/packages/firestore/e2e/helpers.js b/packages/firestore/e2e/helpers.js index 3291e6dc63..99cab707d3 100644 --- a/packages/firestore/e2e/helpers.js +++ b/packages/firestore/e2e/helpers.js @@ -18,7 +18,7 @@ const { getE2eTestProject, getE2eEmulatorHost } = require('../../app/e2e/helpers * */ -exports.wipe = async function wipe(debug = false) { +exports.wipe = async function wipe(debug = false, databaseId = '(default)') { const deleteOptions = { method: 'DELETE', headers: { @@ -27,7 +27,7 @@ exports.wipe = async function wipe(debug = false) { }, port: 8080, host: getE2eEmulatorHost(), - path: '/emulator/v1/projects/' + getE2eTestProject() + '/databases/(default)/documents', + path: '/emulator/v1/projects/' + getE2eTestProject() + `/databases/${databaseId}/documents`, }; try { diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCollectionModule.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCollectionModule.m index 9700238c1e..963f6fec11 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCollectionModule.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCollectionModule.m @@ -63,6 +63,7 @@ - (void)invalidate { RCT_EXPORT_METHOD(namedQueryOnSnapshot : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)name : (NSString *)type : (NSArray *)filters @@ -74,31 +75,35 @@ - (void)invalidate { return; } - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; - [[FIRFirestore firestore] getQueryNamed:name - completion:^(FIRQuery *query) { - if (query == nil) { - [self sendSnapshotError:firebaseApp - listenerId:listenerId - error:nil]; - return; - } - - RNFBFirestoreQuery *firestoreQuery = - [[RNFBFirestoreQuery alloc] initWithModifiers:firestore - query:query - filters:filters - orders:orders - options:options]; - [self handleQueryOnSnapshot:firebaseApp - firestoreQuery:firestoreQuery - listenerId:listenerId - listenerOptions:listenerOptions]; - }]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; + [firestore getQueryNamed:name + completion:^(FIRQuery *query) { + if (query == nil) { + [self sendSnapshotError:firebaseApp + databaseId:databaseId + listenerId:listenerId + error:nil]; + return; + } + + RNFBFirestoreQuery *firestoreQuery = + [[RNFBFirestoreQuery alloc] initWithModifiers:firestore + query:query + filters:filters + orders:orders + options:options]; + [self handleQueryOnSnapshot:firebaseApp + databaseId:databaseId + firestoreQuery:firestoreQuery + listenerId:listenerId + listenerOptions:listenerOptions]; + }]; } RCT_EXPORT_METHOD(collectionOnSnapshot : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (NSString *)type : (NSArray *)filters @@ -110,7 +115,8 @@ - (void)invalidate { return; } - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRQuery *query = [RNFBFirestoreCommon getQueryForFirestore:firestore path:path type:type]; RNFBFirestoreQuery *firestoreQuery = [[RNFBFirestoreQuery alloc] initWithModifiers:firestore @@ -119,12 +125,16 @@ - (void)invalidate { orders:orders options:options]; [self handleQueryOnSnapshot:firebaseApp + databaseId:databaseId firestoreQuery:firestoreQuery listenerId:listenerId listenerOptions:listenerOptions]; } -RCT_EXPORT_METHOD(collectionOffSnapshot : (FIRApp *)firebaseApp : (nonnull NSNumber *)listenerId) { +RCT_EXPORT_METHOD(collectionOffSnapshot + : (FIRApp *)firebaseApp + : (NSString *)databaseId + : (nonnull NSNumber *)listenerId) { id listener = collectionSnapshotListeners[listenerId]; if (listener) { [listener remove]; @@ -134,6 +144,7 @@ - (void)invalidate { RCT_EXPORT_METHOD(namedQueryGet : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)name : (NSString *)type : (NSArray *)filters @@ -142,31 +153,33 @@ - (void)invalidate { : (NSDictionary *)getOptions : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; - [[FIRFirestore firestore] - getQueryNamed:name - completion:^(FIRQuery *query) { - if (query == nil) { - return [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:nil]; - } - - RNFBFirestoreQuery *firestoreQuery = - [[RNFBFirestoreQuery alloc] initWithModifiers:firestore - query:query - filters:filters - orders:orders - options:options]; - FIRFirestoreSource source = [self getSource:getOptions]; - [self handleQueryGet:firebaseApp - firestoreQuery:firestoreQuery - source:source - resolve:resolve - reject:reject]; - }]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; + [firestore getQueryNamed:name + completion:^(FIRQuery *query) { + if (query == nil) { + return [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:nil]; + } + + RNFBFirestoreQuery *firestoreQuery = + [[RNFBFirestoreQuery alloc] initWithModifiers:firestore + query:query + filters:filters + orders:orders + options:options]; + FIRFirestoreSource source = [self getSource:getOptions]; + [self handleQueryGet:firebaseApp + databaseId:databaseId + firestoreQuery:firestoreQuery + source:source + resolve:resolve + reject:reject]; + }]; } RCT_EXPORT_METHOD(collectionCount : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (NSString *)type : (NSArray *)filters @@ -174,7 +187,8 @@ - (void)invalidate { : (NSDictionary *)options : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRQuery *query = [RNFBFirestoreCommon getQueryForFirestore:firestore path:path type:type]; RNFBFirestoreQuery *firestoreQuery = [[RNFBFirestoreQuery alloc] initWithModifiers:firestore query:query @@ -204,6 +218,7 @@ - (void)invalidate { RCT_EXPORT_METHOD(collectionGet : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (NSString *)type : (NSArray *)filters @@ -212,7 +227,8 @@ - (void)invalidate { : (NSDictionary *)getOptions : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRQuery *query = [RNFBFirestoreCommon getQueryForFirestore:firestore path:path type:type]; RNFBFirestoreQuery *firestoreQuery = [[RNFBFirestoreQuery alloc] initWithModifiers:firestore @@ -222,6 +238,7 @@ - (void)invalidate { options:options]; FIRFirestoreSource source = [self getSource:getOptions]; [self handleQueryGet:firebaseApp + databaseId:databaseId firestoreQuery:firestoreQuery source:source resolve:resolve @@ -229,6 +246,7 @@ - (void)invalidate { } - (void)handleQueryOnSnapshot:(FIRApp *)firebaseApp + databaseId:(NSString *)databaseId firestoreQuery:(RNFBFirestoreQuery *)firestoreQuery listenerId:(nonnull NSNumber *)listenerId listenerOptions:(NSDictionary *)listenerOptions { @@ -245,9 +263,13 @@ - (void)handleQueryOnSnapshot:(FIRApp *)firebaseApp [listener remove]; [collectionSnapshotListeners removeObjectForKey:listenerId]; } - [weakSelf sendSnapshotError:firebaseApp listenerId:listenerId error:error]; + [weakSelf sendSnapshotError:firebaseApp + databaseId:databaseId + listenerId:listenerId + error:error]; } else { [weakSelf sendSnapshotEvent:firebaseApp + databaseId:databaseId listenerId:listenerId snapshot:snapshot includeMetadataChanges:includeMetadataChanges]; @@ -261,6 +283,7 @@ - (void)handleQueryOnSnapshot:(FIRApp *)firebaseApp } - (void)handleQueryGet:(FIRApp *)firebaseApp + databaseId:(NSString *)databaseId firestoreQuery:(RNFBFirestoreQuery *)firestoreQuery source:(FIRFirestoreSource)source resolve:(RCTPromiseResolveBlock)resolve @@ -277,13 +300,15 @@ - (void)handleQueryGet:(FIRApp *)firebaseApp [RNFBFirestoreSerialize querySnapshotToDictionary:@"get" snapshot:snapshot includeMetadataChanges:false - appName:appName]; + appName:appName + databaseId:databaseId]; resolve(serialized); } }]; } - (void)sendSnapshotEvent:(FIRApp *)firApp + databaseId:(NSString *)databaseId listenerId:(nonnull NSNumber *)listenerId snapshot:(FIRQuerySnapshot *)snapshot includeMetadataChanges:(BOOL)includeMetadataChanges { @@ -292,11 +317,13 @@ - (void)sendSnapshotEvent:(FIRApp *)firApp [RNFBFirestoreSerialize querySnapshotToDictionary:@"onSnapshot" snapshot:snapshot includeMetadataChanges:includeMetadataChanges - appName:appName]; + appName:appName + databaseId:databaseId]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_FIRESTORE_COLLECTION_SYNC body:@{ @"appName" : [RNFBSharedUtils getAppJavaScriptName:firApp.name], + @"databaseId" : databaseId, @"listenerId" : listenerId, @"body" : @{ @"snapshot" : serialized, @@ -305,6 +332,7 @@ - (void)sendSnapshotEvent:(FIRApp *)firApp } - (void)sendSnapshotError:(FIRApp *)firApp + databaseId:(NSString *)databaseId listenerId:(nonnull NSNumber *)listenerId error:(NSError *)error { NSArray *codeAndMessage = [RNFBFirestoreCommon getCodeAndMessage:error]; @@ -312,6 +340,7 @@ - (void)sendSnapshotError:(FIRApp *)firApp sendEventWithName:RNFB_FIRESTORE_COLLECTION_SYNC body:@{ @"appName" : [RNFBSharedUtils getAppJavaScriptName:firApp.name], + @"databaseId" : databaseId, @"listenerId" : listenerId, @"body" : @{ @"error" : @{ diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.h b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.h index a97090773a..b2cd6d2fac 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.h +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.h @@ -23,9 +23,13 @@ + (dispatch_queue_t)getFirestoreQueue; -+ (FIRFirestore *)getFirestoreForApp:(FIRApp *)firebaseApp; ++ (FIRFirestore *)getFirestoreForApp:(FIRApp *)firebaseApp databaseId:(NSString *)databaseId; -+ (void)setFirestoreSettings:(FIRFirestore *)firestore appName:(NSString *)appName; ++ (NSString *)createFirestoreKeyWithAppName:(NSString *)appName databaseId:(NSString *)databaseId; + ++ (void)setFirestoreSettings:(FIRFirestore *)firestore + appName:(NSString *)appName + databaseId:(NSString *)databaseId; + (FIRDocumentReference *)getDocumentForFirestore:(FIRFirestore *)firestore path:(NSString *)path; diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.m index 9a5aa19edf..36237670d0 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreCommon.m @@ -29,26 +29,33 @@ NSMutableDictionary *instanceCache; @implementation RNFBFirestoreCommon -+ (FIRFirestore *)getFirestoreForApp:(FIRApp *)app { ++ (FIRFirestore *)getFirestoreForApp:(FIRApp *)app databaseId:(NSString *)databaseId { if (instanceCache == nil) { instanceCache = [[NSMutableDictionary alloc] init]; } - - FIRFirestore *cachedInstance = instanceCache[[app name]]; + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:[app name] + databaseId:databaseId]; + FIRFirestore *cachedInstance = instanceCache[firestoreKey]; if (cachedInstance) { return cachedInstance; } - FIRFirestore *instance = [FIRFirestore firestoreForApp:app]; + FIRFirestore *instance = [FIRFirestore firestoreForApp:app database:databaseId]; - [self setFirestoreSettings:instance appName:[RNFBSharedUtils getAppJavaScriptName:app.name]]; + [self setFirestoreSettings:instance + appName:[RNFBSharedUtils getAppJavaScriptName:app.name] + databaseId:databaseId]; instanceCache[[app name]] = instance; return instance; } ++ (NSString *)createFirestoreKeyWithAppName:(NSString *)appName databaseId:(NSString *)databaseId { + return [NSString stringWithFormat:@"%@:%@", appName, databaseId]; +} + + (dispatch_queue_t)getFirestoreQueue { static dispatch_queue_t firestoreQueue; static dispatch_once_t once; @@ -59,13 +66,18 @@ + (dispatch_queue_t)getFirestoreQueue { return firestoreQueue; } -+ (void)setFirestoreSettings:(FIRFirestore *)firestore appName:(NSString *)appName { ++ (void)setFirestoreSettings:(FIRFirestore *)firestore + appName:(NSString *)appName + databaseId:(NSString *)databaseId { FIRFirestoreSettings *firestoreSettings = [[FIRFirestoreSettings alloc] init]; RNFBPreferences *preferences = [RNFBPreferences shared]; firestoreSettings.dispatchQueue = [self getFirestoreQueue]; - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_CACHE_SIZE, appName]; + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; + + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_CACHE_SIZE, firestoreKey]; NSInteger size = [preferences getIntegerValue:cacheKey defaultValue:0]; if (size == -1) { @@ -76,16 +88,17 @@ + (void)setFirestoreSettings:(FIRFirestore *)firestore appName:(NSString *)appNa firestoreSettings.cacheSizeBytes = size; } - NSString *hostKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_HOST, appName]; + NSString *hostKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_HOST, firestoreKey]; firestoreSettings.host = [preferences getStringValue:hostKey defaultValue:firestore.settings.host]; - NSString *persistenceKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_PERSISTENCE, appName]; + NSString *persistenceKey = + [NSString stringWithFormat:@"%@_%@", FIRESTORE_PERSISTENCE, firestoreKey]; firestoreSettings.persistenceEnabled = (BOOL)[preferences getBooleanValue:persistenceKey defaultValue:firestore.settings.persistenceEnabled]; - NSString *sslKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_SSL, appName]; + NSString *sslKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_SSL, firestoreKey]; firestoreSettings.sslEnabled = (BOOL)[preferences getBooleanValue:sslKey defaultValue:firestore.settings.sslEnabled]; diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreDocumentModule.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreDocumentModule.m index 3770f7d426..2805b6d34e 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreDocumentModule.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreDocumentModule.m @@ -63,6 +63,7 @@ - (void)invalidate { RCT_EXPORT_METHOD(documentOnSnapshot : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (nonnull NSNumber *)listenerId : (NSDictionary *)listenerOptions) { @@ -70,7 +71,8 @@ - (void)invalidate { return; } - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRDocumentReference *documentReference = [RNFBFirestoreCommon getDocumentForFirestore:firestore path:path]; @@ -82,9 +84,15 @@ - (void)invalidate { [listener remove]; [documentSnapshotListeners removeObjectForKey:listenerId]; } - [weakSelf sendSnapshotError:firebaseApp listenerId:listenerId error:error]; + [weakSelf sendSnapshotError:firebaseApp + databaseId:databaseId + listenerId:listenerId + error:error]; } else { - [weakSelf sendSnapshotEvent:firebaseApp listenerId:listenerId snapshot:snapshot]; + [weakSelf sendSnapshotEvent:firebaseApp + databaseId:databaseId + listenerId:listenerId + snapshot:snapshot]; } }; @@ -99,7 +107,10 @@ - (void)invalidate { documentSnapshotListeners[listenerId] = listener; } -RCT_EXPORT_METHOD(documentOffSnapshot : (FIRApp *)firebaseApp : (nonnull NSNumber *)listenerId) { +RCT_EXPORT_METHOD(documentOffSnapshot + : (FIRApp *)firebaseApp + : (NSString *)databaseId + : (nonnull NSNumber *)listenerId) { id listener = documentSnapshotListeners[listenerId]; if (listener) { [listener remove]; @@ -109,11 +120,13 @@ - (void)invalidate { RCT_EXPORT_METHOD(documentGet : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (NSDictionary *)getOptions : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRDocumentReference *documentReference = [RNFBFirestoreCommon getDocumentForFirestore:firestore path:path]; @@ -139,9 +152,12 @@ - (void)invalidate { error:error]; } else { NSString *appName = [RNFBSharedUtils getAppJavaScriptName:firebaseApp.name]; + NSString *firestoreKey = + [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; NSDictionary *serialized = [RNFBFirestoreSerialize documentSnapshotToDictionary:snapshot - appName:appName]; + firestoreKey:firestoreKey]; resolve(serialized); } }]; @@ -149,10 +165,12 @@ - (void)invalidate { RCT_EXPORT_METHOD(documentDelete : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRDocumentReference *documentReference = [RNFBFirestoreCommon getDocumentForFirestore:firestore path:path]; @@ -167,12 +185,14 @@ - (void)invalidate { RCT_EXPORT_METHOD(documentSet : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (NSDictionary *)data : (NSDictionary *)options : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRDocumentReference *documentReference = [RNFBFirestoreCommon getDocumentForFirestore:firestore path:path]; @@ -199,11 +219,13 @@ - (void)invalidate { RCT_EXPORT_METHOD(documentUpdate : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSString *)path : (NSDictionary *)data : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRDocumentReference *documentReference = [RNFBFirestoreCommon getDocumentForFirestore:firestore path:path]; @@ -222,10 +244,12 @@ - (void)invalidate { RCT_EXPORT_METHOD(documentBatch : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSArray *)writes : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRWriteBatch *batch = [firestore batch]; for (NSDictionary *write in writes) { @@ -263,15 +287,19 @@ - (void)invalidate { } - (void)sendSnapshotEvent:(FIRApp *)firApp + databaseId:(NSString *)databaseId listenerId:(nonnull NSNumber *)listenerId snapshot:(FIRDocumentSnapshot *)snapshot { NSString *appName = [RNFBSharedUtils getAppJavaScriptName:firApp.name]; + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; NSDictionary *serialized = [RNFBFirestoreSerialize documentSnapshotToDictionary:snapshot - appName:appName]; + firestoreKey:firestoreKey]; [[RNFBRCTEventEmitter shared] sendEventWithName:RNFB_FIRESTORE_DOCUMENT_SYNC body:@{ @"appName" : [RNFBSharedUtils getAppJavaScriptName:firApp.name], + @"databaseId" : databaseId, @"listenerId" : listenerId, @"body" : @{ @"snapshot" : serialized, @@ -280,6 +308,7 @@ - (void)sendSnapshotEvent:(FIRApp *)firApp } - (void)sendSnapshotError:(FIRApp *)firApp + databaseId:(NSString *)databaseId listenerId:(nonnull NSNumber *)listenerId error:(NSError *)error { NSArray *codeAndMessage = [RNFBFirestoreCommon getCodeAndMessage:error]; @@ -287,6 +316,7 @@ - (void)sendSnapshotError:(FIRApp *)firApp sendEventWithName:RNFB_FIRESTORE_DOCUMENT_SYNC body:@{ @"appName" : [RNFBSharedUtils getAppJavaScriptName:firApp.name], + @"databaseId" : databaseId, @"listenerId" : listenerId, @"body" : @{ @"error" : @{ diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m index 38e5b312e2..2563487aeb 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m @@ -45,9 +45,10 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(disableNetwork : (FIRApp *)firebaseApp + : (NSString *)databaseId : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] + [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp databaseId:databaseId] disableNetworkWithCompletion:^(NSError *error) { if (error) { [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; @@ -59,9 +60,10 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(enableNetwork : (FIRApp *)firebaseApp + : (NSString *)databaseId : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] + [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp databaseId:databaseId] enableNetworkWithCompletion:^(NSError *error) { if (error) { [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; @@ -73,36 +75,40 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(settings : (FIRApp *)firebaseApp + : (NSString *)databaseId : (NSDictionary *)settings : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { NSString *appName = [RNFBSharedUtils getAppJavaScriptName:firebaseApp.name]; + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; if (settings[@"cacheSizeBytes"]) { - NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_CACHE_SIZE, appName]; + NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_CACHE_SIZE, firestoreKey]; [[RNFBPreferences shared] setIntegerValue:cacheKey integerValue:[settings[@"cacheSizeBytes"] integerValue]]; } if (settings[@"host"]) { - NSString *hostKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_HOST, appName]; + NSString *hostKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_HOST, firestoreKey]; [[RNFBPreferences shared] setStringValue:hostKey stringValue:settings[@"host"]]; } if (settings[@"persistence"]) { - NSString *persistenceKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_PERSISTENCE, appName]; + NSString *persistenceKey = + [NSString stringWithFormat:@"%@_%@", FIRESTORE_PERSISTENCE, firestoreKey]; [[RNFBPreferences shared] setBooleanValue:persistenceKey boolValue:[settings[@"persistence"] boolValue]]; } if (settings[@"ssl"]) { - NSString *sslKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_SSL, appName]; + NSString *sslKey = [NSString stringWithFormat:@"%@_%@", FIRESTORE_SSL, firestoreKey]; [[RNFBPreferences shared] setBooleanValue:sslKey boolValue:[settings[@"ssl"] boolValue]]; } if (settings[@"serverTimestampBehavior"]) { NSString *key = - [NSString stringWithFormat:@"%@_%@", FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR, appName]; + [NSString stringWithFormat:@"%@_%@", FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR, firestoreKey]; [[RNFBPreferences shared] setStringValue:key stringValue:settings[@"serverTimestampBehavior"]]; } @@ -111,11 +117,12 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(loadBundle : (FIRApp *)firebaseApp + : (NSString *)databaseId : (nonnull NSString *)bundle : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { NSData *bundleData = [bundle dataUsingEncoding:NSUTF8StringEncoding]; - [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] + [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp databaseId:databaseId] loadBundle:bundleData completion:^(FIRLoadBundleTaskProgress *progress, NSError *error) { if (error) { @@ -128,9 +135,10 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(clearPersistence : (FIRApp *)firebaseApp + : (NSString *)databaseId : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] + [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp databaseId:databaseId] clearPersistenceWithCompletion:^(NSError *error) { if (error) { [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; @@ -142,15 +150,20 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(useEmulator : (FIRApp *)firebaseApp + : (NSString *)databaseId : (nonnull NSString *)host : (NSInteger)port) { if (emulatorConfigs == nil) { emulatorConfigs = [[NSMutableDictionary alloc] init]; } - if (!emulatorConfigs[firebaseApp.name]) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:firebaseApp.name + databaseId:databaseId]; + if (!emulatorConfigs[firestoreKey]) { + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; [firestore useEmulatorWithHost:host port:port]; - emulatorConfigs[firebaseApp.name] = @YES; + emulatorConfigs[firestoreKey] = @YES; // It is not sufficient to just use emulator. You have toggle SSL off too. FIRFirestoreSettings *settings = firestore.settings; @@ -161,9 +174,10 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(waitForPendingWrites : (FIRApp *)firebaseApp + : (NSString *)databaseId : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp] + [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp databaseId:databaseId] waitForPendingWritesWithCompletion:^(NSError *error) { if (error) { [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; @@ -175,15 +189,19 @@ + (BOOL)requiresMainQueueSetup { RCT_EXPORT_METHOD(terminate : (FIRApp *)firebaseApp + : (NSString *)databaseId : (RCTPromiseResolveBlock)resolve : (RCTPromiseRejectBlock)reject) { - FIRFirestore *instance = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *instance = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; [instance terminateWithCompletion:^(NSError *error) { if (error) { [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; } else { - [instanceCache removeObjectForKey:[firebaseApp name]]; + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:firebaseApp.name + databaseId:databaseId]; + [instanceCache removeObjectForKey:firestoreKey]; resolve(nil); } }]; diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.h b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.h index 180c37224a..6c601af7af 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.h +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.h @@ -24,14 +24,16 @@ + (NSDictionary *)querySnapshotToDictionary:(NSString *)source snapshot:(FIRQuerySnapshot *)snapshot includeMetadataChanges:(BOOL)includeMetadataChanges - appName:(NSString *)appName; + appName:(NSString *)appName + databaseId:(NSString *)databaseId; + (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange isMetadataChange:(BOOL)isMetadataChange - appName:(NSString *)appName; + appName:(NSString *)appName + databaseId:(NSString *)databaseId; + (NSDictionary *)documentSnapshotToDictionary:(FIRDocumentSnapshot *)snapshot - appName:(NSString *)appName; + firestoreKey:(NSString *)firestoreKey; + (NSDictionary *)serializeDictionary:(NSDictionary *)dictionary; diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.m index 09bbf943ee..33f20e0a57 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreSerialize.m @@ -65,7 +65,8 @@ @implementation RNFBFirestoreSerialize + (NSDictionary *)querySnapshotToDictionary:(NSString *)source snapshot:(FIRQuerySnapshot *)snapshot includeMetadataChanges:(BOOL)includeMetadataChanges - appName:(NSString *)appName { + appName:(NSString *)appName + databaseId:(NSString *)databaseId { NSMutableArray *metadata = [[NSMutableArray alloc] init]; NSMutableDictionary *snapshotMap = [[NSMutableDictionary alloc] init]; @@ -84,7 +85,8 @@ + (NSDictionary *)querySnapshotToDictionary:(NSString *)source for (FIRDocumentChange *documentChange in documentChangesList) { [changes addObject:[self documentChangeToDictionary:documentChange isMetadataChange:false - appName:appName]]; + appName:appName + databaseId:databaseId]]; } } else { // If listening to metadata changes, get the changes list with document changes array. @@ -119,16 +121,19 @@ + (NSDictionary *)querySnapshotToDictionary:(NSString *)source [changes addObject:[self documentChangeToDictionary:documentMetadataChange isMetadataChange:isMetadataChange - appName:appName]]; + appName:appName + databaseId:databaseId]]; } } snapshotMap[KEY_CHANGES] = changes; - + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; // set documents NSMutableArray *documents = [[NSMutableArray alloc] init]; for (FIRDocumentSnapshot *documentSnapshot in documentSnapshots) { - [documents addObject:[self documentSnapshotToDictionary:documentSnapshot appName:appName]]; + [documents addObject:[self documentSnapshotToDictionary:documentSnapshot + firestoreKey:firestoreKey]]; } snapshotMap[KEY_DOCUMENTS] = documents; @@ -143,7 +148,8 @@ + (NSDictionary *)querySnapshotToDictionary:(NSString *)source + (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange isMetadataChange:(BOOL)isMetadataChange - appName:(NSString *)appName { + appName:(NSString *)appName + databaseId:(NSString *)databaseId { NSMutableDictionary *changeMap = [[NSMutableDictionary alloc] init]; changeMap[@"isMetadataChange"] = @(isMetadataChange); @@ -154,9 +160,10 @@ + (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange } else { changeMap[KEY_DOC_CHANGE_TYPE] = CHANGE_REMOVED; } - + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; changeMap[KEY_DOC_CHANGE_DOCUMENT] = [self documentSnapshotToDictionary:documentChange.document - appName:appName]; + firestoreKey:firestoreKey]; // Note the Firestore C++ SDK here returns a maxed UInt that is != NSUIntegerMax, so we make one // ourselves so we can convert to -1 for JS land @@ -180,7 +187,7 @@ + (NSDictionary *)documentChangeToDictionary:(FIRDocumentChange *)documentChange // Native DocumentSnapshot -> NSDictionary (for JS) + (NSDictionary *)documentSnapshotToDictionary:(FIRDocumentSnapshot *)snapshot - appName:(NSString *)appName { + firestoreKey:(NSString *)firestoreKey { NSMutableArray *metadata = [[NSMutableArray alloc] init]; NSMutableDictionary *documentMap = [[NSMutableDictionary alloc] init]; @@ -194,7 +201,7 @@ + (NSDictionary *)documentSnapshotToDictionary:(FIRDocumentSnapshot *)snapshot if (snapshot.exists) { NSString *key = - [NSString stringWithFormat:@"%@_%@", FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR, appName]; + [NSString stringWithFormat:@"%@_%@", FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR, firestoreKey]; NSString *behavior = [[RNFBPreferences shared] getStringValue:key defaultValue:@"none"]; FIRServerTimestampBehavior serverTimestampBehavior; diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreTransactionModule.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreTransactionModule.m index 53088c235f..bf3dad6dda 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreTransactionModule.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreTransactionModule.m @@ -61,6 +61,7 @@ - (void)invalidate { RCT_EXPORT_METHOD(transactionGetDocument : (FIRApp *)firebaseApp + : (NSString *)databaseId : (nonnull NSNumber *)transactionId : (NSString *)path : (RCTPromiseResolveBlock)resolve @@ -75,7 +76,8 @@ - (void)invalidate { NSError *error = nil; FIRTransaction *transaction = [transactionState valueForKey:@"transaction"]; - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; FIRDocumentReference *ref = [RNFBFirestoreCommon getDocumentForFirestore:firestore path:path]; FIRDocumentSnapshot *snapshot = [transaction getDocument:ref error:&error]; @@ -83,8 +85,10 @@ - (void)invalidate { [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error]; } else { NSString *appName = [RNFBSharedUtils getAppJavaScriptName:firebaseApp.name]; - NSDictionary *snapshotDict = [RNFBFirestoreSerialize documentSnapshotToDictionary:snapshot - appName:appName]; + NSString *firestoreKey = [RNFBFirestoreCommon createFirestoreKeyWithAppName:appName + databaseId:databaseId]; + NSDictionary *snapshotDict = + [RNFBFirestoreSerialize documentSnapshotToDictionary:snapshot firestoreKey:firestoreKey]; NSString *snapshotPath = snapshotDict[@"path"]; if (snapshotPath == nil) { @@ -96,7 +100,10 @@ - (void)invalidate { } } -RCT_EXPORT_METHOD(transactionDispose : (FIRApp *)firebaseApp : (nonnull NSNumber *)transactionId) { +RCT_EXPORT_METHOD(transactionDispose + : (FIRApp *)firebaseApp + : (NSString *)databaseId + : (nonnull NSNumber *)transactionId) { @synchronized(transactions[[transactionId stringValue]]) { NSMutableDictionary *transactionState = transactions[[transactionId stringValue]]; @@ -112,6 +119,7 @@ - (void)invalidate { RCT_EXPORT_METHOD(transactionApplyBuffer : (FIRApp *)firebaseApp + : (NSString *)databaseId : (nonnull NSNumber *)transactionId : (NSArray *)commandBuffer) { @synchronized(transactions[[transactionId stringValue]]) { @@ -128,8 +136,12 @@ - (void)invalidate { } } -RCT_EXPORT_METHOD(transactionBegin : (FIRApp *)firebaseApp : (nonnull NSNumber *)transactionId) { - FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp]; +RCT_EXPORT_METHOD(transactionBegin + : (FIRApp *)firebaseApp + : (NSString *)databaseId + : (nonnull NSNumber *)transactionId) { + FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp + databaseId:databaseId]; __block BOOL aborted = false; __block NSMutableDictionary *transactionState = [NSMutableDictionary new]; @@ -153,6 +165,7 @@ - (void)invalidate { body:@{ @"listenerId" : transactionId, @"appName" : [RNFBSharedUtils getAppJavaScriptName:firebaseApp.name], + @"databaseId" : databaseId, @"body" : eventMap, }]; }); @@ -241,6 +254,7 @@ - (void)invalidate { body:@{ @"listenerId" : transactionId, @"appName" : [RNFBSharedUtils getAppJavaScriptName:firebaseApp.name], + @"databaseId" : databaseId, @"body" : eventMap, }]; } @@ -252,4 +266,4 @@ - (void)invalidate { [firestore runTransactionWithBlock:transactionBlock completion:completionBlock]; } -@end \ No newline at end of file +@end diff --git a/packages/firestore/lib/index.d.ts b/packages/firestore/lib/index.d.ts index 6747e705ec..ed816e2cfe 100644 --- a/packages/firestore/lib/index.d.ts +++ b/packages/firestore/lib/index.d.ts @@ -2367,7 +2367,7 @@ declare module '@react-native-firebase/app' { >; } interface FirebaseApp { - firestore(): FirebaseFirestoreTypes.Module; + firestore(databaseId?: string): FirebaseFirestoreTypes.Module; } } } diff --git a/packages/firestore/lib/index.js b/packages/firestore/lib/index.js index f6e7d6c992..16cd0d2ad9 100644 --- a/packages/firestore/lib/index.js +++ b/packages/firestore/lib/index.js @@ -57,8 +57,13 @@ const nativeEvents = [ ]; class FirebaseFirestoreModule extends FirebaseModule { - constructor(app, config) { + constructor(app, config, databaseId) { super(app, config); + if (isString(databaseId) || databaseId === undefined) { + this._customUrlOrRegion = databaseId || '(default)'; + } else if (!isString(databaseId)) { + throw new Error('firebase.app().firestore(*) database ID must be a string'); + } this._referencePath = new FirestorePath(); this._transactionHandler = new FirestoreTransactionHandler(this); @@ -81,6 +86,10 @@ class FirebaseFirestoreModule extends FirebaseModule { ignoreUndefinedProperties: false, }; } + // We override the FirebaseModule's `eventNameForApp()` method to include the customUrlOrRegion + eventNameForApp(...args) { + return `${this.app.name}-${this._customUrlOrRegion}-${args.join('-')}`; + } batch() { return new FirestoreWriteBatch(this); @@ -372,7 +381,7 @@ export default createModuleNamespace({ nativeModuleName, nativeEvents, hasMultiAppSupport: true, - hasCustomUrlOrRegionSupport: false, + hasCustomUrlOrRegionSupport: true, ModuleClass: FirebaseFirestoreModule, }); diff --git a/packages/firestore/lib/modular/index.d.ts b/packages/firestore/lib/modular/index.d.ts index c161da7ae0..c1f8ef8007 100644 --- a/packages/firestore/lib/modular/index.d.ts +++ b/packages/firestore/lib/modular/index.d.ts @@ -117,6 +117,18 @@ export declare function getFirestore(app: FirebaseApp): Firestore; export function getFirestore(app?: FirebaseApp): Firestore; +/** + * Returns the existing default {@link Firestore} instance that is associated with the + * provided {@link @firebase/app#FirebaseApp} and database ID. If no instance exists, initializes a new + * instance with default settings. + * + * @param app - The {@link @firebase/app#FirebaseApp} instance that the returned {@link Firestore} + * instance is associated with. + * @param databaseId - The ID of the Firestore database to use. If not provided, the default database is used. + * @returns The {@link Firestore} + */ +export declare function getFirestore(app?: FirebaseApp, databaseId?: string): Firestore; + /** * Gets a `DocumentReference` instance that refers to the document at the * specified absolute path. diff --git a/packages/firestore/lib/modular/index.js b/packages/firestore/lib/modular/index.js index d6e8d52921..ee6552fc5c 100644 --- a/packages/firestore/lib/modular/index.js +++ b/packages/firestore/lib/modular/index.js @@ -15,14 +15,22 @@ import { firebase } from '../index'; /** * @param {FirebaseApp?} app + * @param {String?} databaseId * @returns {Firestore} */ -export function getFirestore(app) { +export function getFirestore(app, databaseId) { if (app) { - return firebase.firestore(app); + if (databaseId) { + return firebase.app(app.name).firestore(databaseId); + } else { + return firebase.app(app.name).firestore(); + } + } + if (databaseId) { + return firebase.app().firestore(databaseId); } - return firebase.firestore(); + return firebase.app().firestore(); } /** diff --git a/packages/firestore/lib/web/RNFBFirestoreModule.js b/packages/firestore/lib/web/RNFBFirestoreModule.js index 3ffd5ce550..e8ffe63f94 100644 --- a/packages/firestore/lib/web/RNFBFirestoreModule.js +++ b/packages/firestore/lib/web/RNFBFirestoreModule.js @@ -68,19 +68,24 @@ function getCachedAppInstance(appName) { return (appInstances[appName] ??= getApp(appName)); } +function createFirestoreKey(appName, databaseId) { + return `${appName}:${databaseId}`; +} + // Returns a cached Firestore instance. -function getCachedFirestoreInstance(appName) { - let instance = firestoreInstances[appName]; +function getCachedFirestoreInstance(appName, databaseId) { + const firestoreKey = createFirestoreKey(appName, databaseId); + let instance = firestoreInstances[firestoreKey]; if (!instance) { - instance = getFirestore(getCachedAppInstance(appName)); - if (emulatorForApp[appName]) { + instance = getFirestore(getCachedAppInstance(appName), databaseId); + if (emulatorForApp[firestoreKey]) { connectFirestoreEmulator( instance, - emulatorForApp[appName].host, - emulatorForApp[appName].port, + emulatorForApp[firestoreKey].host, + emulatorForApp[firestoreKey].port, ); } - firestoreInstances[appName] = instance; + firestoreInstances[firestoreKey] = instance; } return instance; } @@ -126,27 +131,30 @@ export default { /** * Use the Firestore emulator. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} host - The emulator host. * @param {number} port - The emulator port. * @returns {Promise} An empty promise. */ - useEmulator(appName, host, port) { + useEmulator(appName, databaseId, host, port) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); connectFirestoreEmulator(firestore, host, port); - emulatorForApp[appName] = { host, port }; + const firestoreKey = createFirestoreKey(appName, databaseId); + emulatorForApp[firestoreKey] = { host, port }; }); }, /** * Initializes a Firestore instance with settings. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {object} settings - The Firestore settings. * @returns {Promise} An empty promise. */ - settings(appName, settings) { + settings(appName, databaseId, settings) { return guard(() => { - const instance = initializeFirestore(getCachedAppInstance(appName), settings); + const instance = initializeFirestore(getCachedAppInstance(appName), settings, databaseId); firestoreInstances[appName] = instance; }); }, @@ -154,11 +162,12 @@ export default { /** * Terminates a Firestore instance. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @returns {Promise} An empty promise. */ - terminate(appName) { + terminate(appName, databaseId) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); await terminate(firestore); return null; }); @@ -184,6 +193,7 @@ export default { /** * Get a collection count from Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} path - The collection path. * @param {string} type - The collection type (e.g. collectionGroup). * @param {object[]} filters - The collection filters. @@ -191,9 +201,9 @@ export default { * @param {object} options - The collection options. * @returns {Promise} The collection count object. */ - collectionCount(appName, path, type, filters, orders, options) { + collectionCount(appName, databaseId, path, type, filters, orders, options) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const queryRef = type === 'collectionGroup' ? collectionGroup(firestore, path) : collection(firestore, path); const query = buildQuery(queryRef, filters, orders, options); @@ -208,6 +218,7 @@ export default { /** * Get a collection from Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} path - The collection path. * @param {string} type - The collection type (e.g. collectionGroup). * @param {object[]} filters - The collection filters. @@ -216,7 +227,7 @@ export default { * @param {object} getOptions - The get options. * @returns {Promise} The collection object. */ - collectionGet(appName, path, type, filters, orders, options, getOptions) { + collectionGet(appName, databaseId, path, type, filters, orders, options, getOptions) { if (getOptions && getOptions.source === 'cache') { return rejectWithCodeAndMessage( 'unsupported', @@ -225,7 +236,7 @@ export default { } return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const queryRef = type === 'collectionGroup' ? collectionGroup(firestore, path) : collection(firestore, path); const query = buildQuery(queryRef, filters, orders, options); @@ -246,11 +257,12 @@ export default { /** * Get a document from Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} path - The document path. * @param {object} getOptions - The get options. * @returns {Promise} The document object. */ - documentGet(appName, path, getOptions) { + documentGet(appName, databaseId, path, getOptions) { return guard(async () => { if (getOptions && getOptions.source === 'cache') { return rejectWithCodeAndMessage( @@ -259,7 +271,7 @@ export default { ); } - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const ref = doc(firestore, path); const snapshot = await getDoc(ref); return documentSnapshotToObject(snapshot); @@ -269,12 +281,13 @@ export default { /** * Delete a document from Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} path - The document path. * @returns {Promise} An empty promise. */ - documentDelete(appName, path) { + documentDelete(appName, databaseId, path) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const ref = doc(firestore, path); await deleteDoc(ref); return null; @@ -284,14 +297,15 @@ export default { /** * Set a document in Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} path - The document path. * @param {object} data - The document data. * @param {object} options - The set options. * @returns {Promise} An empty promise. */ - documentSet(appName, path, data, options) { + documentSet(appName, databaseId, path, data, options) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const ref = doc(firestore, path); const setOptions = {}; if ('merge' in options) { @@ -306,13 +320,14 @@ export default { /** * Update a document in Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} path - The document path. * @param {object} data - The document data. * @returns {Promise} An empty promise. */ - documentUpdate(appName, path, data) { + documentUpdate(appName, databaseId, path, data) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const ref = doc(firestore, path); await updateDoc(ref, readableToObject(firestore, data)); }); @@ -321,11 +336,12 @@ export default { /** * Batch write documents in Firestore. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {object[]} writes - The document writes in write batches format. */ - documentBatch(appName, writes) { + documentBatch(appName, databaseId, writes) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const batch = writeBatch(firestore); const writesArray = parseDocumentBatches(firestore, writes); @@ -360,11 +376,12 @@ export default { /** * Get a document from a Firestore transaction. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} transactionId - The transaction id. * @param {string} path - The document path. * @returns {Promise} The document object. */ - transactionGetDocument(appName, transactionId, path) { + transactionGetDocument(appName, databaseId, transactionId, path) { if (!transactionHandler[transactionId]) { return rejectWithCodeAndMessage( 'internal-error', @@ -373,7 +390,7 @@ export default { } return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); const docRef = doc(firestore, path); const tsx = transactionHandler[transactionId]; const snapshot = await tsx.get(docRef); @@ -384,9 +401,10 @@ export default { /** * Dispose a transaction instance. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} transactionId - The transaction id. */ - transactionDispose(appName, transactionId) { + transactionDispose(appName, databaseId, transactionId) { // There's no abort method in the JS SDK, so we just remove the transaction handler. delete transactionHandler[transactionId]; }, @@ -394,10 +412,11 @@ export default { /** * Applies a buffer of commands to a Firestore transaction. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} transactionId - The transaction id. * @param {object[]} commandBuffer - The readable array of buffer commands. */ - transactionApplyBuffer(appName, transactionId, commandBuffer) { + transactionApplyBuffer(appName, databaseId, transactionId, commandBuffer) { if (transactionHandler[transactionId]) { transactionBuffer[transactionId] = commandBuffer; } @@ -406,12 +425,13 @@ export default { /** * Begins a Firestore transaction. * @param {string} appName - The app name. + * @param {string} databaseId - The database ID. * @param {string} transactionId - The transaction id. * @returns {Promise} An empty promise. */ - transactionBegin(appName, transactionId) { + transactionBegin(appName, databaseId, transactionId) { return guard(async () => { - const firestore = getCachedFirestoreInstance(appName); + const firestore = getCachedFirestoreInstance(appName, databaseId); try { await runTransaction(firestore, async tsx => { @@ -421,6 +441,7 @@ export default { eventName: 'firestore_transaction_event', body: { type: 'update' }, appName, + databaseId, listenerId: transactionId, }); @@ -468,6 +489,7 @@ export default { eventName: 'firestore_transaction_event', body: { type: 'complete' }, appName, + databaseId, listenerId: transactionId, }); } catch (e) { @@ -475,6 +497,7 @@ export default { eventName: 'firestore_transaction_event', body: { type: 'error', error: getWebError(e) }, appName, + databaseId, listenerId: transactionId, }); } diff --git a/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java b/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java index 02d60cf06a..27f4cddaec 100644 --- a/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java +++ b/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java @@ -281,7 +281,8 @@ public void setMaxUploadRetryTime(String appName, double milliseconds, Promise p * @link https://firebase.google.com/docs/reference/js/firebase.storage.Storage#useEmulator */ @ReactMethod - public void useEmulator(String appName, String host, int port, String bucketUrl, Promise promise) { + public void useEmulator( + String appName, String host, int port, String bucketUrl, Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseStorage firebaseStorage = FirebaseStorage.getInstance(firebaseApp, bucketUrl); diff --git a/tests/app.js b/tests/app.js index 0240e33038..ebb4018f35 100644 --- a/tests/app.js +++ b/tests/app.js @@ -81,6 +81,7 @@ function loadTests(_) { firebase.auth().useEmulator('http://localhost:9099'); if (platformSupportedModules.includes('firestore')) { firebase.firestore().useEmulator('localhost', 8080); + firebase.app().firestore('second-rnfb').useEmulator('localhost', 8080); // Firestore caches documents locally (a great feature!) and that confounds tests // as data from previous runs pollutes following runs until re-install the app. Clear it. if (!Platform.other) { diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index 6b9e7fa7de..c1aa026f45 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -1326,76 +1326,78 @@ PODS: - React-logger (= 0.73.4) - React-perflogger (= 0.73.4) - RecaptchaInterop (100.0.0) + - RNCAsyncStorage (1.24.0): + - React-Core - RNDeviceInfo (11.1.0): - React-Core - - RNFBAnalytics (20.1.0): + - RNFBAnalytics (20.3.0): - Firebase/Analytics (= 10.29.0) - GoogleAppMeasurementOnDeviceConversion (= 10.29.0) - React-Core - RNFBApp - - RNFBApp (20.1.0): + - RNFBApp (20.3.0): - Firebase/CoreOnly (= 10.29.0) - React-Core - - RNFBAppCheck (20.1.0): + - RNFBAppCheck (20.3.0): - Firebase/AppCheck (= 10.29.0) - React-Core - RNFBApp - - RNFBAppDistribution (20.1.0): + - RNFBAppDistribution (20.3.0): - Firebase/AppDistribution (= 10.29.0) - React-Core - RNFBApp - - RNFBAuth (20.1.0): + - RNFBAuth (20.3.0): - Firebase/Auth (= 10.29.0) - React-Core - RNFBApp - - RNFBCrashlytics (20.1.0): + - RNFBCrashlytics (20.3.0): - Firebase/Crashlytics (= 10.29.0) - FirebaseCoreExtension - React-Core - RNFBApp - - RNFBDatabase (20.1.0): + - RNFBDatabase (20.3.0): - Firebase/Database (= 10.29.0) - React-Core - RNFBApp - - RNFBDynamicLinks (20.1.0): + - RNFBDynamicLinks (20.3.0): - Firebase/DynamicLinks (= 10.29.0) - GoogleUtilities/AppDelegateSwizzler - React-Core - RNFBApp - - RNFBFirestore (20.1.0): + - RNFBFirestore (20.3.0): - Firebase/Firestore (= 10.29.0) - nanopb (< 2.30910.0, >= 2.30908.0) - React-Core - RNFBApp - - RNFBFunctions (20.1.0): + - RNFBFunctions (20.3.0): - Firebase/Functions (= 10.29.0) - React-Core - RNFBApp - - RNFBInAppMessaging (20.1.0): + - RNFBInAppMessaging (20.3.0): - Firebase/InAppMessaging (= 10.29.0) - React-Core - RNFBApp - - RNFBInstallations (20.1.0): + - RNFBInstallations (20.3.0): - Firebase/Installations (= 10.29.0) - React-Core - RNFBApp - - RNFBMessaging (20.1.0): + - RNFBMessaging (20.3.0): - Firebase/Messaging (= 10.29.0) - FirebaseCoreExtension - React-Core - RNFBApp - - RNFBML (20.1.0): + - RNFBML (20.3.0): - React-Core - RNFBApp - - RNFBPerf (20.1.0): + - RNFBPerf (20.3.0): - Firebase/Performance (= 10.29.0) - React-Core - RNFBApp - - RNFBRemoteConfig (20.1.0): + - RNFBRemoteConfig (20.3.0): - Firebase/RemoteConfig (= 10.29.0) - React-Core - RNFBApp - - RNFBStorage (20.1.0): + - RNFBStorage (20.3.0): - Firebase/Storage (= 10.29.0) - React-Core - RNFBApp @@ -1454,6 +1456,7 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - RNDeviceInfo (from `../node_modules/react-native-device-info`) - "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)" - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" @@ -1621,6 +1624,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/utils" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RNCAsyncStorage: + :path: "../node_modules/@react-native-async-storage/async-storage" RNDeviceInfo: :path: "../node_modules/react-native-device-info" RNFBAnalytics: @@ -1758,26 +1763,27 @@ SPEC CHECKSUMS: React-utils: 21a798438d45e70ed9c2e2fe0894ee32ba7b7c5b ReactCommon: dcc65c813041388dead6c8b477444757425ce961 RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21 + RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a RNDeviceInfo: b899ce37a403a4dea52b7cb85e16e49c04a5b88e - RNFBAnalytics: 8aa2c79f8ced7036e14907e6ae86f9f55bd1cf49 - RNFBApp: 94776f5e68f403793b86b91dc6a22ac6f03cf51f - RNFBAppCheck: 920940691bdd352b023c091a07d867d2ef968abe - RNFBAppDistribution: 50c6178315a8ac419cdbf09e2b3657c80ad9b80d - RNFBAuth: 76bde6ea67e2d6a1c250867f35bcd7c21e9a914c - RNFBCrashlytics: a63b5d5d9e02589fe8a3a59c4805bde4b9261972 - RNFBDatabase: 6448909a9f07d2b2f6c39f6765fd0d125ce378e9 - RNFBDynamicLinks: 4e181c96ad07a4d310aedcd37d3030aad9859c23 - RNFBFirestore: 54a9a3dacaa311bc1016d8c22a2acdc0009260bc - RNFBFunctions: 8392e6225e2cccf0022cea05cdf3122787de86b8 - RNFBInAppMessaging: 4ea54b07b8aa30c252508f3683ca2dee36c0e74b - RNFBInstallations: eb59cb11bdc16c13feddfd9ad0c8c02a652287cc - RNFBMessaging: ee80027da5c5eba48109c5066dc568fb98f9c27d - RNFBML: 617f875def61029d0c4632af07bde4be4c76bff0 - RNFBPerf: 1853af83b06f70099c30baeae84de22d1fce55c2 - RNFBRemoteConfig: 68b8f0a65dcb7c06af92a2b4c7cc674703019ccf - RNFBStorage: a569fc000b2ff3befeb2aa7d2c6455ca0c21da7c + RNFBAnalytics: 921cce283e56e0775b10708bd1856eb5d44011ef + RNFBApp: a7aff07a7f212149539fce51a1326c6d208b03ad + RNFBAppCheck: 2463063b94cf3178e0398175636bcc3766f4f98e + RNFBAppDistribution: 35f4080726886e005c9af0097ebcc624bfca8aad + RNFBAuth: 3ed676e60d0ca3b2c71023240658e1f02bc752d7 + RNFBCrashlytics: 0194114803bf2984cf241fd79198a14c575b317e + RNFBDatabase: d46a1d1cbcf3769179deaed11e74753999e6f3a1 + RNFBDynamicLinks: 5c83930d0ba2478501e1e330322f1ef57059720d + RNFBFirestore: 2c8d40c5a28007d6a3d183e21141e2fedf7f6d41 + RNFBFunctions: bb2c9cf33f64efddf8852d1077e599436f99fa9c + RNFBInAppMessaging: 8f2cb9ebfbd83c1923e44822ddd585aebd20c71d + RNFBInstallations: e4ba9770162024c6471d3ce3c9cfe5690207e041 + RNFBMessaging: f1fdc7ac62df96e3c8362d516caaf84fab69085d + RNFBML: e4045427926875c7a469332f1e75fd96999d8202 + RNFBPerf: 818dc56dee8203dbdf5ab2bef2675a62c0df2978 + RNFBRemoteConfig: 3d9c04beffe742f407d89d2b295a02420df4d2e8 + RNFBStorage: 5c67f0b3d55ef904b87ad7826ce0d588c2e028fb SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: 95dd7da90f1ff7fdc1017f409737e31d23b1c46a From 0b0de45513cd3aab9f7037fd8468a63cf96aa62c Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Tue, 13 Aug 2024 10:58:46 +0100 Subject: [PATCH 2/5] feat(firestore): support for `PersistentCacheIndexManager` (#7910) --- .../firestore/__tests__/firestore.test.ts | 46 +++++ .../ReactNativeFirebaseFirestoreModule.java | 29 +++ packages/firestore/e2e/firestore.e2e.js | 188 +++++++++++++++++- .../ios/RNFBFirestore/RNFBFirestoreModule.m | 33 +++ .../FirestorePersistentCacheIndexManager.js | 34 ++++ packages/firestore/lib/index.d.ts | 31 +++ packages/firestore/lib/index.js | 14 ++ packages/firestore/lib/modular/index.d.ts | 40 ++++ packages/firestore/lib/modular/index.js | 44 ++++ .../firestore/lib/web/RNFBFirestoreModule.js | 4 + 10 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 packages/firestore/lib/FirestorePersistentCacheIndexManager.js diff --git a/packages/firestore/__tests__/firestore.test.ts b/packages/firestore/__tests__/firestore.test.ts index 423dad6faa..28e232c53f 100644 --- a/packages/firestore/__tests__/firestore.test.ts +++ b/packages/firestore/__tests__/firestore.test.ts @@ -51,6 +51,10 @@ import firestore, { deleteDoc, onSnapshot, Timestamp, + getPersistentCacheIndexManager, + deleteAllPersistentCacheIndexes, + disablePersistentCacheIndexAutoCreation, + enablePersistentCacheIndexAutoCreation, } from '../lib'; const COLLECTION = 'firestore'; @@ -629,5 +633,47 @@ describe('Firestore', function () { it('`Timestamp` is properly exposed to end user', function () { expect(Timestamp).toBeDefined(); }); + + it('`getPersistentCacheIndexManager` is properly exposed to end user', function () { + expect(getPersistentCacheIndexManager).toBeDefined(); + const indexManager = getPersistentCacheIndexManager(firebase.firestore()); + expect(indexManager!.constructor.name).toEqual('FirestorePersistentCacheIndexManager'); + }); + + it('`deleteAllPersistentCacheIndexes` is properly exposed to end user', function () { + expect(deleteAllPersistentCacheIndexes).toBeDefined(); + }); + + it('`disablePersistentCacheIndexAutoCreation` is properly exposed to end user', function () { + expect(disablePersistentCacheIndexAutoCreation).toBeDefined(); + }); + + it('`enablePersistentCacheIndexAutoCreation` is properly exposed to end user', function () { + expect(enablePersistentCacheIndexAutoCreation).toBeDefined(); + }); + }); + + describe('FirestorePersistentCacheIndexManager', function () { + it('is exposed to end user', function () { + const firestore1 = firebase.firestore(); + firestore1.settings({ persistence: true }); + const indexManager = firestore1.persistentCacheIndexManager(); + expect(indexManager).toBeDefined(); + expect(indexManager.constructor.name).toEqual('FirestorePersistentCacheIndexManager'); + + expect(indexManager.enableIndexAutoCreation).toBeInstanceOf(Function); + expect(indexManager.disableIndexAutoCreation).toBeInstanceOf(Function); + expect(indexManager.deleteAllIndexes).toBeInstanceOf(Function); + + const firestore2 = firebase.firestore(); + firestore2.settings({ persistence: false }); + + const nullIndexManager = firestore2.persistentCacheIndexManager(); + + expect(nullIndexManager).toBeNull(); + + const nullIndexManagerModular = getPersistentCacheIndexManager(firestore2); + expect(nullIndexManagerModular).toBeNull(); + }); }); }); 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 d3405533ef..b8f5ceaab4 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 @@ -20,6 +20,7 @@ import static io.invertase.firebase.common.RCTConvertFirebase.toHashMap; import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreCommon.rejectPromiseFirestoreException; import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.createFirestoreKey; +import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.getFirestoreForApp; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; @@ -29,6 +30,7 @@ import com.facebook.react.bridge.WritableMap; import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.LoadBundleTaskProgress; +import com.google.firebase.firestore.PersistentCacheIndexManager; import io.invertase.firebase.common.ReactNativeFirebaseModule; public class ReactNativeFirebaseFirestoreModule extends ReactNativeFirebaseModule { @@ -164,6 +166,33 @@ public void terminate(String appName, String databaseId, Promise promise) { }); } + @ReactMethod + public void persistenceCacheIndexManager( + String appName, String databaseId, int requestType, Promise promise) { + PersistentCacheIndexManager indexManager = + getFirestoreForApp(appName, databaseId).getPersistentCacheIndexManager(); + if (indexManager != null) { + switch (requestType) { + case 0: + indexManager.enableIndexAutoCreation(); + break; + case 1: + indexManager.disableIndexAutoCreation(); + break; + case 2: + indexManager.deleteAllIndexes(); + break; + } + } else { + promise.reject( + "firestore/index-manager-null", + "`PersistentCacheIndexManager` is not available, persistence has not been enabled for" + + " Firestore"); + return; + } + promise.resolve(null); + } + private WritableMap taskProgressToWritableMap(LoadBundleTaskProgress progress) { WritableMap writableMap = Arguments.createMap(); writableMap.putDouble("bytesLoaded", progress.getBytesLoaded()); diff --git a/packages/firestore/e2e/firestore.e2e.js b/packages/firestore/e2e/firestore.e2e.js index 6d711fa205..da0f08ae08 100644 --- a/packages/firestore/e2e/firestore.e2e.js +++ b/packages/firestore/e2e/firestore.e2e.js @@ -114,7 +114,8 @@ describe('firestore()', function () { describe('disableNetwork() & enableNetwork()', function () { it('disables and enables with no errors', async function () { - if (Platform.other) { + if (!(Platform.android || Platform.ios)) { + // Not supported on web lite sdk return; } @@ -211,6 +212,7 @@ describe('firestore()', function () { it("handles 'estimate'", async function () { // TODO(ehesp): Figure out how to call settings on other. if (Platform.other) { + // Not supported on web lite sdk return; } @@ -237,6 +239,7 @@ describe('firestore()', function () { it("handles 'previous'", async function () { // TODO(ehesp): Figure out how to call settings on other. if (Platform.other) { + // Not supported on web lite sdk return; } @@ -331,6 +334,88 @@ describe('firestore()', function () { }); }); }); + + describe('FirestorePersistentCacheIndexManager', function () { + describe('if persistence is enabled', function () { + it('should enableIndexAutoCreation()', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + + const db = firebase.firestore(); + const indexManager = db.persistentCacheIndexManager(); + await indexManager.enableIndexAutoCreation(); + }); + + it('should disableIndexAutoCreation()', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const db = firebase.firestore(); + const indexManager = db.persistentCacheIndexManager(); + await indexManager.disableIndexAutoCreation(); + }); + + it('should deleteAllIndexes()', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const db = firebase.firestore(); + const indexManager = db.persistentCacheIndexManager(); + await indexManager.deleteAllIndexes(); + }); + }); + + describe('if persistence is disabled', function () { + it('should return `null` when calling `persistentCacheIndexManager()`', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const secondFirestore = firebase.app('secondaryFromNative').firestore(); + secondFirestore.settings({ persistence: false }); + const indexManager = secondFirestore.persistentCacheIndexManager(); + should.equal(indexManager, null); + }); + }); + + describe('macOS should throw exception when calling `persistentCacheIndexManager()`', function () { + it('should throw an exception when calling PersistentCacheIndexManager API', async function () { + if (!Platform.other) { + return; + } + const db = firebase.firestore(); + const indexManager = db.persistentCacheIndexManager(); + + try { + await indexManager.enableIndexAutoCreation(); + + throw new Error('Did not throw an Error.'); + } catch (e) { + e.message.should.containEql('Not supported in the lite SDK'); + } + + try { + await indexManager.deleteAllIndexes(); + + throw new Error('Did not throw an Error.'); + } catch (e) { + e.message.should.containEql('Not supported in the lite SDK'); + } + + try { + await indexManager.disableIndexAutoCreation(); + + throw new Error('Did not throw an Error.'); + } catch (e) { + e.message.should.containEql('Not supported in the lite SDK'); + } + }); + }); + }); }); describe('modular', function () { @@ -683,5 +768,106 @@ describe('firestore()', function () { }); }); }); + + describe('FirestorePersistentCacheIndexManager', function () { + describe('if persistence is enabled', function () { + it('should enableIndexAutoCreation()', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const { + getFirestore, + getPersistentCacheIndexManager, + enablePersistentCacheIndexAutoCreation, + } = firestoreModular; + const db = getFirestore(); + const indexManager = getPersistentCacheIndexManager(db); + await enablePersistentCacheIndexAutoCreation(indexManager); + }); + + it('should disableIndexAutoCreation()', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const { + getFirestore, + getPersistentCacheIndexManager, + disablePersistentCacheIndexAutoCreation, + } = firestoreModular; + const db = getFirestore(); + const indexManager = getPersistentCacheIndexManager(db); + await disablePersistentCacheIndexAutoCreation(indexManager); + }); + + it('should deleteAllIndexes()', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const { getFirestore, getPersistentCacheIndexManager, deleteAllPersistentCacheIndexes } = + firestoreModular; + const db = getFirestore(); + const indexManager = getPersistentCacheIndexManager(db); + await deleteAllPersistentCacheIndexes(indexManager); + }); + }); + + describe('if persistence is disabled', function () { + it('should return `null` when calling `persistentCacheIndexManager()`', async function () { + if (Platform.other) { + // Not supported on web lite sdk + return; + } + const { initializeFirestore, getPersistentCacheIndexManager } = firestoreModular; + const { getApp } = modular; + const app = getApp('secondaryFromNative'); + const db = await initializeFirestore(app, { persistence: false }); + + const indexManager = getPersistentCacheIndexManager(db); + should.equal(indexManager, null); + }); + }); + + describe('macOS should throw exception when calling `persistentCacheIndexManager()`', function () { + it('should throw an exception when calling PersistentCacheIndexManager API', async function () { + if (Platform.android || Platform.ios) { + return; + } + const { + getFirestore, + getPersistentCacheIndexManager, + enablePersistentCacheIndexAutoCreation, + disablePersistentCacheIndexAutoCreation, + deleteAllPersistentCacheIndexes, + } = firestoreModular; + + const db = getFirestore(); + const indexManager = getPersistentCacheIndexManager(db); + + try { + await enablePersistentCacheIndexAutoCreation(indexManager); + throw new Error('Did not throw an Error.'); + } catch (e) { + e.message.should.containEql('Not supported in the lite SDK'); + } + + try { + await disablePersistentCacheIndexAutoCreation(indexManager); + throw new Error('Did not throw an Error.'); + } catch (e) { + e.message.should.containEql('Not supported in the lite SDK'); + } + + try { + await deleteAllPersistentCacheIndexes(indexManager); + throw new Error('Did not throw an Error.'); + } catch (e) { + e.message.should.containEql('Not supported in the lite SDK'); + } + }); + }); + }); }); }); diff --git a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m index 2563487aeb..bdb1a1de48 100644 --- a/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m +++ b/packages/firestore/ios/RNFBFirestore/RNFBFirestoreModule.m @@ -17,6 +17,7 @@ #import "RNFBFirestoreModule.h" #import +#import "FirebaseFirestoreInternal/FIRPersistentCacheIndexManager.h" #import "RNFBFirestoreCommon.h" #import "RNFBPreferences.h" @@ -207,6 +208,38 @@ + (BOOL)requiresMainQueueSetup { }]; } +RCT_EXPORT_METHOD(persistenceCacheIndexManager + : (FIRApp *)firebaseApp + : (NSString *)databaseId + : (NSInteger)requestType + : (RCTPromiseResolveBlock)resolve + : (RCTPromiseRejectBlock)reject) { + FIRPersistentCacheIndexManager *persistentCacheIndexManager = + [RNFBFirestoreCommon getFirestoreForApp:firebaseApp databaseId:databaseId] + .persistentCacheIndexManager; + + if (persistentCacheIndexManager) { + switch (requestType) { + case 0: + [persistentCacheIndexManager enableIndexAutoCreation]; + break; + case 1: + [persistentCacheIndexManager disableIndexAutoCreation]; + break; + case 2: + [persistentCacheIndexManager deleteAllIndexes]; + break; + } + } else { + reject(@"firestore/index-manager-null", + @"`PersistentCacheIndexManager` is not available, persistence has not been enabled for " + @"Firestore", + nil); + return; + } + resolve(nil); +} + - (NSMutableDictionary *)taskProgressToDictionary:(FIRLoadBundleTaskProgress *)progress { NSMutableDictionary *progressMap = [[NSMutableDictionary alloc] init]; progressMap[@"bytesLoaded"] = @(progress.bytesLoaded); diff --git a/packages/firestore/lib/FirestorePersistentCacheIndexManager.js b/packages/firestore/lib/FirestorePersistentCacheIndexManager.js new file mode 100644 index 0000000000..13ccade8dd --- /dev/null +++ b/packages/firestore/lib/FirestorePersistentCacheIndexManager.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export default class FirestorePersistentCacheIndexManager { + constructor(firestore) { + this._firestore = firestore; + } + + async enableIndexAutoCreation() { + await this._firestore.native.persistenceCacheIndexManager(0); + } + + async disableIndexAutoCreation() { + await this._firestore.native.persistenceCacheIndexManager(1); + } + + async deleteAllIndexes() { + await this._firestore.native.persistenceCacheIndexManager(2); + } +} diff --git a/packages/firestore/lib/index.d.ts b/packages/firestore/lib/index.d.ts index ed816e2cfe..261250473c 100644 --- a/packages/firestore/lib/index.d.ts +++ b/packages/firestore/lib/index.d.ts @@ -2005,6 +2005,29 @@ export namespace FirebaseFirestoreTypes { ): WriteBatch; } + /** + * Returns the PersistentCache Index Manager used by the given Firestore object. + * The PersistentCacheIndexManager instance, or null if local persistent storage is not in use. + */ + export interface PersistentCacheIndexManager { + /** + * Enables the SDK to create persistent cache indexes automatically for local query + * execution when the SDK believes cache indexes can help improves performance. + * This feature is disabled by default. + */ + enableIndexAutoCreation(): Promise; + /** + * Stops creating persistent cache indexes automatically for local query execution. + * The indexes which have been created by calling `enableIndexAutoCreation()` still take effect. + */ + disableIndexAutoCreation(): Promise; + /** + * Removes all persistent cache indexes. Note this function also deletes indexes + * generated by `setIndexConfiguration()`, which is deprecated. + */ + deleteAllIndexes(): Promise; + } + /** * Represents the state of bundle loading tasks. * @@ -2321,6 +2344,14 @@ export namespace FirebaseFirestoreTypes { * @param port: emulator port (eg, 8080) */ useEmulator(host: string, port: number): void; + + /** + * Gets the `PersistentCacheIndexManager` instance used by this Cloud Firestore instance. + * This is not the same as Cloud Firestore Indexes. + * Persistent cache indexes are optional indexes that only exist within the SDK to assist in local query execution. + * Returns `null` if local persistent storage is not in use. + */ + persistentCacheIndexManager(): PersistentCacheIndexManager | null; } /** diff --git a/packages/firestore/lib/index.js b/packages/firestore/lib/index.js index 16cd0d2ad9..08a5cfafea 100644 --- a/packages/firestore/lib/index.js +++ b/packages/firestore/lib/index.js @@ -40,6 +40,7 @@ import FirestoreTransactionHandler from './FirestoreTransactionHandler'; import FirestoreWriteBatch from './FirestoreWriteBatch'; import version from './version'; import fallBackModule from './web/RNFBFirestoreModule'; +import FirestorePersistentCacheIndexManager from './FirestorePersistentCacheIndexManager'; const namespace = 'firestore'; @@ -84,6 +85,7 @@ class FirebaseFirestoreModule extends FirebaseModule { this._settings = { ignoreUndefinedProperties: false, + persistence: true, }; } // We override the FirebaseModule's `eventNameForApp()` method to include the customUrlOrRegion @@ -363,8 +365,20 @@ class FirebaseFirestoreModule extends FirebaseModule { delete settings.ignoreUndefinedProperties; } + if (settings.persistence === false) { + // Required for persistentCacheIndexManager(), if this setting is `false`, it returns `null` + this._settings.persistence = false; + } + return this.native.settings(settings); } + + persistentCacheIndexManager() { + if (this._settings.persistence === false) { + return null; + } + return new FirestorePersistentCacheIndexManager(this); + } } // import { SDK_VERSION } from '@react-native-firebase/firestore'; diff --git a/packages/firestore/lib/modular/index.d.ts b/packages/firestore/lib/modular/index.d.ts index c1f8ef8007..179bf65a80 100644 --- a/packages/firestore/lib/modular/index.d.ts +++ b/packages/firestore/lib/modular/index.d.ts @@ -9,6 +9,7 @@ import DocumentData = FirebaseFirestoreTypes.DocumentData; import Query = FirebaseFirestoreTypes.Query; import FieldValue = FirebaseFirestoreTypes.FieldValue; import FieldPath = FirebaseFirestoreTypes.FieldPath; +import PersistentCacheIndexManager = FirebaseFirestoreTypes.PersistentCacheIndexManager; /** Primitive types. */ export type Primitive = string | number | boolean | undefined | null; @@ -545,6 +546,45 @@ export function namedQuery(firestore: Firestore, name: string): Query} - A promise that resolves when the operation is complete. + */ +export function enablePersistentCacheIndexAutoCreation( + indexManager: PersistentCacheIndexManager, +): Promise; +/** + * Stops creating persistent cache indexes automatically for local query execution. + * The indexes which have been created by calling `enableIndexAutoCreation()` still take effect. + * @param {PersistentCacheIndexManager} - The `PersistentCacheIndexManager` instance. + * @return {Promise} - A promise that resolves when the operation is complete. + */ +export function disablePersistentCacheIndexAutoCreation( + indexManager: PersistentCacheIndexManager, +): Promise; +/** + * Removes all persistent cache indexes. Note this function also deletes indexes + * generated by `setIndexConfiguration()`, which is deprecated. + * @param {PersistentCacheIndexManager} - The `PersistentCacheIndexManager` instance. + * @return {Promise} - A promise that resolves when the operation is complete. + */ +export function deleteAllPersistentCacheIndexes( + indexManager: PersistentCacheIndexManager, +): Promise; + export * from './query'; export * from './snapshot'; export * from './Bytes'; diff --git a/packages/firestore/lib/modular/index.js b/packages/firestore/lib/modular/index.js index ee6552fc5c..46eb2d8c4c 100644 --- a/packages/firestore/lib/modular/index.js +++ b/packages/firestore/lib/modular/index.js @@ -8,6 +8,7 @@ * @typedef {import('..').FirebaseFirestoreTypes.Query} Query * @typedef {import('..').FirebaseFirestoreTypes.SetOptions} SetOptions * @typedef {import('..').FirebaseFirestoreTypes.Settings} FirestoreSettings + * @typedef {import('..').FirebaseFirestoreTypes.PersistentCacheIndexManager} PersistentCacheIndexManager * @typedef {import('@firebase/app').FirebaseApp} FirebaseApp */ @@ -217,6 +218,49 @@ export function writeBatch(firestore) { return firestore.batch(); } +/** + * Gets the `PersistentCacheIndexManager` instance used by this Cloud Firestore instance. + * This is not the same as Cloud Firestore Indexes. + * Persistent cache indexes are optional indexes that only exist within the SDK to assist in local query execution. + * Returns `null` if local persistent storage is not in use. + * @param {Firestore} firestore + * @returns {PersistentCacheIndexManager | null} + */ +export function getPersistentCacheIndexManager(firestore) { + return firestore.persistentCacheIndexManager(); +} + +/** + * Enables the SDK to create persistent cache indexes automatically for local query + * execution when the SDK believes cache indexes can help improves performance. + * This feature is disabled by default. + * @param {PersistentCacheIndexManager} indexManager + * @returns {Promise Date: Tue, 13 Aug 2024 11:32:15 +0000 Subject: [PATCH 3/5] chore(release): release packages --- CHANGELOG.md | 12 +++ lerna.json | 2 +- packages/analytics/CHANGELOG.md | 6 ++ packages/analytics/package.json | 4 +- packages/app-check/CHANGELOG.md | 4 + packages/app-check/package.json | 4 +- packages/app-distribution/CHANGELOG.md | 4 + packages/app-distribution/package.json | 4 +- packages/app/CHANGELOG.md | 6 ++ packages/app/package.json | 2 +- packages/auth/CHANGELOG.md | 4 + packages/auth/package.json | 4 +- packages/crashlytics/CHANGELOG.md | 4 + packages/crashlytics/package.json | 4 +- packages/database/CHANGELOG.md | 6 ++ packages/database/package.json | 4 +- packages/dynamic-links/CHANGELOG.md | 4 + packages/dynamic-links/package.json | 4 +- packages/firestore/CHANGELOG.md | 7 ++ packages/firestore/package.json | 4 +- packages/functions/CHANGELOG.md | 4 + packages/functions/package.json | 4 +- packages/in-app-messaging/CHANGELOG.md | 4 + packages/in-app-messaging/package.json | 6 +- packages/installations/CHANGELOG.md | 4 + packages/installations/package.json | 4 +- packages/messaging/CHANGELOG.md | 4 + packages/messaging/package.json | 4 +- packages/ml/CHANGELOG.md | 4 + packages/ml/package.json | 4 +- packages/perf/CHANGELOG.md | 4 + packages/perf/package.json | 4 +- packages/remote-config/CHANGELOG.md | 4 + packages/remote-config/package.json | 6 +- packages/storage/CHANGELOG.md | 6 ++ packages/storage/package.json | 4 +- tests/CHANGELOG.md | 6 ++ tests/package.json | 36 ++++----- yarn.lock | 104 ++++++++++++------------- 39 files changed, 203 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f812735186..3756d6c6f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Features + +- **firestore:** support for `PersistentCacheIndexManager` ([#7910](https://github.com/invertase/react-native-firebase/issues/7910)) ([0b0de45](https://github.com/invertase/react-native-firebase/commit/0b0de45513cd3aab9f7037fd8468a63cf96aa62c)) +- **firestore:** support for second database ([#7949](https://github.com/invertase/react-native-firebase/issues/7949)) ([eec08a0](https://github.com/invertase/react-native-firebase/commit/eec08a06f41dd96d13778fbed2afcaaac238fca4)) + +### Bug Fixes + +- **analytics:** update the typing for Item ([#7919](https://github.com/invertase/react-native-firebase/issues/7919)) ([c2c4576](https://github.com/invertase/react-native-firebase/commit/c2c457608c865f6d25e4b9b6689cbc00721de194)) +- **database:** fixes modular imports exports ([#7916](https://github.com/invertase/react-native-firebase/issues/7916)) ([7022204](https://github.com/invertase/react-native-firebase/commit/7022204616f6149e3327cbf2ab8a469f537a7b61)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) ### Features diff --git a/lerna.json b/lerna.json index f7c133f1e0..34fddcf281 100644 --- a/lerna.json +++ b/lerna.json @@ -76,5 +76,5 @@ "userUrlFormat": "{{host}}/{{user}}" }, "ignoreChanges": ["**/docs/**", "**/.github/**", "**/e2e/**", "**/tests/**"], - "version": "20.3.0" + "version": "20.4.0" } diff --git a/packages/analytics/CHANGELOG.md b/packages/analytics/CHANGELOG.md index e2c9f01897..baa71efb86 100644 --- a/packages/analytics/CHANGELOG.md +++ b/packages/analytics/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Bug Fixes + +- **analytics:** update the typing for Item ([#7919](https://github.com/invertase/react-native-firebase/issues/7919)) ([c2c4576](https://github.com/invertase/react-native-firebase/commit/c2c457608c865f6d25e4b9b6689cbc00721de194)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) ### Bug Fixes diff --git a/packages/analytics/package.json b/packages/analytics/package.json index ac4e1b41ba..6309b9e246 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/analytics", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - The analytics module provides out of the box support with Google Analytics for Firebase. Integration with the Android & iOS allows for in-depth analytical insight reporting, such as device information, location, user actions and more.", "main": "lib/index.js", @@ -22,7 +22,7 @@ "analytics" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/app-check/CHANGELOG.md b/packages/app-check/CHANGELOG.md index 2bfbb780d3..10da078acb 100644 --- a/packages/app-check/CHANGELOG.md +++ b/packages/app-check/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/app-check + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/app-check diff --git a/packages/app-check/package.json b/packages/app-check/package.json index 31ec6d9622..28e03f8c53 100644 --- a/packages/app-check/package.json +++ b/packages/app-check/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/app-check", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - App Check", "main": "lib/index.js", @@ -25,7 +25,7 @@ "appCheck" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0", + "@react-native-firebase/app": "20.4.0", "expo": ">=47.0.0" }, "devDependencies": { diff --git a/packages/app-distribution/CHANGELOG.md b/packages/app-distribution/CHANGELOG.md index e4aeaadc22..75b434ae90 100644 --- a/packages/app-distribution/CHANGELOG.md +++ b/packages/app-distribution/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/app-distribution + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/app-distribution diff --git a/packages/app-distribution/package.json b/packages/app-distribution/package.json index 4ea85cad16..16955944cf 100644 --- a/packages/app-distribution/package.json +++ b/packages/app-distribution/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/app-distribution", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - App Distribution", "main": "lib/index.js", @@ -22,7 +22,7 @@ "app-distribution" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 195cd4fdd4..e966b47e04 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Features + +- **firestore:** support for second database ([#7949](https://github.com/invertase/react-native-firebase/issues/7949)) ([eec08a0](https://github.com/invertase/react-native-firebase/commit/eec08a06f41dd96d13778fbed2afcaaac238fca4)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) ### Bug Fixes diff --git a/packages/app/package.json b/packages/app/package.json index 71898c9143..0564e00e5d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/app", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Storage and more.", "main": "lib/index.js", diff --git a/packages/auth/CHANGELOG.md b/packages/auth/CHANGELOG.md index 43cc18abf5..1bc678fcd8 100644 --- a/packages/auth/CHANGELOG.md +++ b/packages/auth/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/auth + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) ### Features diff --git a/packages/auth/package.json b/packages/auth/package.json index e673c5012e..c5e17b373f 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/auth", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - The authentication module provides an easy-to-use API to integrate an authentication workflow into new and existing applications. React Native Firebase provides access to all Firebase authentication methods and identity providers.", "main": "lib/index.js", @@ -27,7 +27,7 @@ "plist": "^3.1.0" }, "peerDependencies": { - "@react-native-firebase/app": "20.3.0", + "@react-native-firebase/app": "20.4.0", "expo": ">=47.0.0" }, "devDependencies": { diff --git a/packages/crashlytics/CHANGELOG.md b/packages/crashlytics/CHANGELOG.md index ddd7ecfef3..44890effe4 100644 --- a/packages/crashlytics/CHANGELOG.md +++ b/packages/crashlytics/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/crashlytics + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/crashlytics diff --git a/packages/crashlytics/package.json b/packages/crashlytics/package.json index 191f79e0de..585109100b 100644 --- a/packages/crashlytics/package.json +++ b/packages/crashlytics/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/crashlytics", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Firebase Crashlytics is a lightweight, realtime crash reporter that helps you track, prioritize, and fix stability issues that erode your app quality. React Native Firebase provides automatic crash reporting for both native and JavaScript errors, including unhandled promise rejections.", "main": "lib/index.js", @@ -29,7 +29,7 @@ "crashlytics" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0", + "@react-native-firebase/app": "20.4.0", "expo": ">=47.0.0" }, "dependencies": { diff --git a/packages/database/CHANGELOG.md b/packages/database/CHANGELOG.md index 5fba11f106..2300c035dd 100644 --- a/packages/database/CHANGELOG.md +++ b/packages/database/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Bug Fixes + +- **database:** fixes modular imports exports ([#7916](https://github.com/invertase/react-native-firebase/issues/7916)) ([7022204](https://github.com/invertase/react-native-firebase/commit/7022204616f6149e3327cbf2ab8a469f537a7b61)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/database diff --git a/packages/database/package.json b/packages/database/package.json index 382cf05ecf..3c11e5803b 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/database", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. React Native Firebase provides native integration with the Android & iOS Firebase SDKs, supporting both realtime data sync and offline capabilities.", "main": "lib/index.js", @@ -25,7 +25,7 @@ "realtome database" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/dynamic-links/CHANGELOG.md b/packages/dynamic-links/CHANGELOG.md index 1feb4cdbc0..7afc843bda 100644 --- a/packages/dynamic-links/CHANGELOG.md +++ b/packages/dynamic-links/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/dynamic-links + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/dynamic-links diff --git a/packages/dynamic-links/package.json b/packages/dynamic-links/package.json index 0d43713ece..3767dfe040 100644 --- a/packages/dynamic-links/package.json +++ b/packages/dynamic-links/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/dynamic-links", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Dynamic Links", "main": "lib/index.js", @@ -25,7 +25,7 @@ "dynamic link" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0", + "@react-native-firebase/app": "20.4.0", "expo": ">=47.0.0" }, "devDependencies": { diff --git a/packages/firestore/CHANGELOG.md b/packages/firestore/CHANGELOG.md index 6aad6f8a74..202d605075 100644 --- a/packages/firestore/CHANGELOG.md +++ b/packages/firestore/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Features + +- **firestore:** support for `PersistentCacheIndexManager` ([#7910](https://github.com/invertase/react-native-firebase/issues/7910)) ([0b0de45](https://github.com/invertase/react-native-firebase/commit/0b0de45513cd3aab9f7037fd8468a63cf96aa62c)) +- **firestore:** support for second database ([#7949](https://github.com/invertase/react-native-firebase/issues/7949)) ([eec08a0](https://github.com/invertase/react-native-firebase/commit/eec08a06f41dd96d13778fbed2afcaaac238fca4)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/firestore diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 80836d8a7c..47b1629886 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/firestore", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Cloud Firestore is a NoSQL cloud database to store and sync data between your React Native application and Firebase's database. The API matches the Firebase Web SDK whilst taking advantage of the native SDKs performance and offline capabilities.", "main": "lib/index.js", @@ -27,7 +27,7 @@ "firestore" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/functions/CHANGELOG.md b/packages/functions/CHANGELOG.md index 01724543ad..f0e3a8eab2 100644 --- a/packages/functions/CHANGELOG.md +++ b/packages/functions/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/functions + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/functions diff --git a/packages/functions/package.json b/packages/functions/package.json index a278a84790..fdd0169ee1 100644 --- a/packages/functions/package.json +++ b/packages/functions/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/functions", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Cloud Functions for Firebase lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests. React Native Firebase supports integration with production and locally emulated Cloud Functions with a simple API interface.\n\n", "main": "lib/index.js", @@ -24,7 +24,7 @@ "functions" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "devDependencies": { "@react-native-firebase/private-tests-firebase-functions": "^0.0.1" diff --git a/packages/in-app-messaging/CHANGELOG.md b/packages/in-app-messaging/CHANGELOG.md index 85fc2a7e82..82a08a9e77 100644 --- a/packages/in-app-messaging/CHANGELOG.md +++ b/packages/in-app-messaging/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/in-app-messaging + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/in-app-messaging diff --git a/packages/in-app-messaging/package.json b/packages/in-app-messaging/package.json index cc226a16e4..e92471dfac 100644 --- a/packages/in-app-messaging/package.json +++ b/packages/in-app-messaging/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/in-app-messaging", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Firebase In-App Messaging helps you engage your app's active users by sending them targeted, contextual messages that encourage them to use key app features. React Native Firebase provides support for both native Android & iOS integration with a simple JavaScript API.", "main": "lib/index.js", @@ -27,8 +27,8 @@ "inAppMessaging" ], "peerDependencies": { - "@react-native-firebase/analytics": "20.3.0", - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/analytics": "20.4.0", + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/installations/CHANGELOG.md b/packages/installations/CHANGELOG.md index 3e9aad0b09..167f7022fd 100644 --- a/packages/installations/CHANGELOG.md +++ b/packages/installations/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/installations + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/installations diff --git a/packages/installations/package.json b/packages/installations/package.json index f9d7bf6a8f..5b07f47d67 100644 --- a/packages/installations/package.json +++ b/packages/installations/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/installations", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Installations", "main": "lib/index.js", @@ -22,7 +22,7 @@ "installations" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/messaging/CHANGELOG.md b/packages/messaging/CHANGELOG.md index 50bdf9884a..705dc51457 100644 --- a/packages/messaging/CHANGELOG.md +++ b/packages/messaging/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/messaging + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/messaging diff --git a/packages/messaging/package.json b/packages/messaging/package.json index 333111ccb6..241e5a1bd5 100644 --- a/packages/messaging/package.json +++ b/packages/messaging/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/messaging", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - React Native Firebase provides native integration of Firebase Cloud Messaging (FCM) for both Android & iOS. FCM is a cost free service, allowing for server-device and device-device communication. The React Native Firebase Messaging module provides a simple JavaScript API to interact with FCM.", "main": "lib/index.js", @@ -24,7 +24,7 @@ "messaging" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0", + "@react-native-firebase/app": "20.4.0", "expo": ">=47.0.0" }, "devDependencies": { diff --git a/packages/ml/CHANGELOG.md b/packages/ml/CHANGELOG.md index ade4836696..954ff56f0f 100644 --- a/packages/ml/CHANGELOG.md +++ b/packages/ml/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/ml + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/ml diff --git a/packages/ml/package.json b/packages/ml/package.json index c4011674bd..e63dd45232 100644 --- a/packages/ml/package.json +++ b/packages/ml/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/ml", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - Firebase ML brings the power of machine learning vision to your React Native application, supporting both Android & iOS.", "main": "lib/index.js", @@ -26,7 +26,7 @@ "image labeler" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/perf/CHANGELOG.md b/packages/perf/CHANGELOG.md index 133ca3bbc5..549c3e4a43 100644 --- a/packages/perf/CHANGELOG.md +++ b/packages/perf/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/perf + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/perf diff --git a/packages/perf/package.json b/packages/perf/package.json index 3ff5280f35..7941c3430f 100644 --- a/packages/perf/package.json +++ b/packages/perf/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/perf", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - React Native Firebase provides native integration with Performance Monitoring to gain insight into key performance characteristics within your React Native application.", "main": "lib/index.js", @@ -29,7 +29,7 @@ "performance monitoring" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0", + "@react-native-firebase/app": "20.4.0", "expo": ">=47.0.0" }, "devDependencies": { diff --git a/packages/remote-config/CHANGELOG.md b/packages/remote-config/CHANGELOG.md index 9ce91771ad..2dc2ea7cd7 100644 --- a/packages/remote-config/CHANGELOG.md +++ b/packages/remote-config/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +**Note:** Version bump only for package @react-native-firebase/remote-config + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/remote-config diff --git a/packages/remote-config/package.json b/packages/remote-config/package.json index 2be505793d..9cc95e3b84 100644 --- a/packages/remote-config/package.json +++ b/packages/remote-config/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/remote-config", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - React Native Firebase provides native integration with Remote Config, allowing you to change the appearance and/or functionality of your app without requiring an app update.", "main": "lib/index.js", @@ -24,8 +24,8 @@ "remote-config" ], "peerDependencies": { - "@react-native-firebase/analytics": "20.3.0", - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/analytics": "20.4.0", + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/storage/CHANGELOG.md b/packages/storage/CHANGELOG.md index f15cecb734..40e8a22b5c 100644 --- a/packages/storage/CHANGELOG.md +++ b/packages/storage/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Features + +- **firestore:** support for second database ([#7949](https://github.com/invertase/react-native-firebase/issues/7949)) ([eec08a0](https://github.com/invertase/react-native-firebase/commit/eec08a06f41dd96d13778fbed2afcaaac238fca4)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package @react-native-firebase/storage diff --git a/packages/storage/package.json b/packages/storage/package.json index ebc5ef58ab..38dca4aba7 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -1,6 +1,6 @@ { "name": "@react-native-firebase/storage", - "version": "20.3.0", + "version": "20.4.0", "author": "Invertase (http://invertase.io)", "description": "React Native Firebase - React Native Firebase provides native integration with Cloud Storage, providing support to upload and download files directly from your device and from your Firebase Cloud Storage bucket.", "main": "lib/index.js", @@ -29,7 +29,7 @@ "download" ], "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" }, "publishConfig": { "access": "public" diff --git a/tests/CHANGELOG.md b/tests/CHANGELOG.md index f72c929eb3..e9b95098d0 100644 --- a/tests/CHANGELOG.md +++ b/tests/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [20.4.0](https://github.com/invertase/react-native-firebase/compare/v20.3.0...v20.4.0) (2024-08-13) + +### Features + +- **firestore:** support for second database ([#7949](https://github.com/invertase/react-native-firebase/issues/7949)) ([eec08a0](https://github.com/invertase/react-native-firebase/commit/eec08a06f41dd96d13778fbed2afcaaac238fca4)) + ## [20.3.0](https://github.com/invertase/react-native-firebase/compare/v20.2.1...v20.3.0) (2024-07-19) **Note:** Version bump only for package react-native-firebase-tests diff --git a/tests/package.json b/tests/package.json index a41717dc73..cd8a4af918 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firebase-tests", - "version": "20.3.0", + "version": "20.4.0", "private": true, "scripts": { "build:clean": "rimraf dist && rimraf android/build && rimraf android/app/build && rimraf android/.gradle && rimraf ios/build && rimraf macos/build", @@ -9,24 +9,24 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "^1.24.0", - "@react-native-firebase/analytics": "20.3.0", - "@react-native-firebase/app": "20.3.0", - "@react-native-firebase/app-check": "20.3.0", - "@react-native-firebase/app-distribution": "20.3.0", + "@react-native-firebase/analytics": "20.4.0", + "@react-native-firebase/app": "20.4.0", + "@react-native-firebase/app-check": "20.4.0", + "@react-native-firebase/app-distribution": "20.4.0", "@react-native-firebase/app-types": "6.7.2", - "@react-native-firebase/auth": "20.3.0", - "@react-native-firebase/crashlytics": "20.3.0", - "@react-native-firebase/database": "20.3.0", - "@react-native-firebase/dynamic-links": "20.3.0", - "@react-native-firebase/firestore": "20.3.0", - "@react-native-firebase/functions": "20.3.0", - "@react-native-firebase/in-app-messaging": "20.3.0", - "@react-native-firebase/installations": "20.3.0", - "@react-native-firebase/messaging": "20.3.0", - "@react-native-firebase/ml": "20.3.0", - "@react-native-firebase/perf": "20.3.0", - "@react-native-firebase/remote-config": "20.3.0", - "@react-native-firebase/storage": "20.3.0", + "@react-native-firebase/auth": "20.4.0", + "@react-native-firebase/crashlytics": "20.4.0", + "@react-native-firebase/database": "20.4.0", + "@react-native-firebase/dynamic-links": "20.4.0", + "@react-native-firebase/firestore": "20.4.0", + "@react-native-firebase/functions": "20.4.0", + "@react-native-firebase/in-app-messaging": "20.4.0", + "@react-native-firebase/installations": "20.4.0", + "@react-native-firebase/messaging": "20.4.0", + "@react-native-firebase/ml": "20.4.0", + "@react-native-firebase/perf": "20.4.0", + "@react-native-firebase/remote-config": "20.4.0", + "@react-native-firebase/storage": "20.4.0", "postinstall-postinstall": "2.1.0", "react": "18.2.0", "react-native": "0.73.4", diff --git a/yarn.lock b/yarn.lock index ccf9a53737..76eb49f570 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5803,21 +5803,21 @@ __metadata: languageName: node linkType: hard -"@react-native-firebase/analytics@npm:20.3.0, @react-native-firebase/analytics@workspace:packages/analytics": +"@react-native-firebase/analytics@npm:20.4.0, @react-native-firebase/analytics@workspace:packages/analytics": version: 0.0.0-use.local resolution: "@react-native-firebase/analytics@workspace:packages/analytics" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/app-check@npm:20.3.0, @react-native-firebase/app-check@workspace:packages/app-check": +"@react-native-firebase/app-check@npm:20.4.0, @react-native-firebase/app-check@workspace:packages/app-check": version: 0.0.0-use.local resolution: "@react-native-firebase/app-check@workspace:packages/app-check" dependencies: expo: "npm:^50.0.19" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 expo: ">=47.0.0" peerDependenciesMeta: expo: @@ -5825,11 +5825,11 @@ __metadata: languageName: unknown linkType: soft -"@react-native-firebase/app-distribution@npm:20.3.0, @react-native-firebase/app-distribution@workspace:packages/app-distribution": +"@react-native-firebase/app-distribution@npm:20.4.0, @react-native-firebase/app-distribution@workspace:packages/app-distribution": version: 0.0.0-use.local resolution: "@react-native-firebase/app-distribution@workspace:packages/app-distribution" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft @@ -5840,7 +5840,7 @@ __metadata: languageName: node linkType: hard -"@react-native-firebase/app@npm:20.3.0, @react-native-firebase/app@workspace:packages/app": +"@react-native-firebase/app@npm:20.4.0, @react-native-firebase/app@workspace:packages/app": version: 0.0.0-use.local resolution: "@react-native-firebase/app@workspace:packages/app" dependencies: @@ -5858,7 +5858,7 @@ __metadata: languageName: unknown linkType: soft -"@react-native-firebase/auth@npm:20.3.0, @react-native-firebase/auth@workspace:packages/auth": +"@react-native-firebase/auth@npm:20.4.0, @react-native-firebase/auth@workspace:packages/auth": version: 0.0.0-use.local resolution: "@react-native-firebase/auth@workspace:packages/auth" dependencies: @@ -5866,7 +5866,7 @@ __metadata: expo: "npm:^50.0.19" plist: "npm:^3.1.0" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 expo: ">=47.0.0" peerDependenciesMeta: expo: @@ -5874,14 +5874,14 @@ __metadata: languageName: unknown linkType: soft -"@react-native-firebase/crashlytics@npm:20.3.0, @react-native-firebase/crashlytics@workspace:packages/crashlytics": +"@react-native-firebase/crashlytics@npm:20.4.0, @react-native-firebase/crashlytics@workspace:packages/crashlytics": version: 0.0.0-use.local resolution: "@react-native-firebase/crashlytics@workspace:packages/crashlytics" dependencies: expo: "npm:^50.0.19" stacktrace-js: "npm:^2.0.2" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 expo: ">=47.0.0" peerDependenciesMeta: expo: @@ -5889,21 +5889,21 @@ __metadata: languageName: unknown linkType: soft -"@react-native-firebase/database@npm:20.3.0, @react-native-firebase/database@workspace:packages/database": +"@react-native-firebase/database@npm:20.4.0, @react-native-firebase/database@workspace:packages/database": version: 0.0.0-use.local resolution: "@react-native-firebase/database@workspace:packages/database" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/dynamic-links@npm:20.3.0, @react-native-firebase/dynamic-links@workspace:packages/dynamic-links": +"@react-native-firebase/dynamic-links@npm:20.4.0, @react-native-firebase/dynamic-links@workspace:packages/dynamic-links": version: 0.0.0-use.local resolution: "@react-native-firebase/dynamic-links@workspace:packages/dynamic-links" dependencies: expo: "npm:^50.0.19" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 expo: ">=47.0.0" peerDependenciesMeta: expo: @@ -5911,48 +5911,48 @@ __metadata: languageName: unknown linkType: soft -"@react-native-firebase/firestore@npm:20.3.0, @react-native-firebase/firestore@workspace:packages/firestore": +"@react-native-firebase/firestore@npm:20.4.0, @react-native-firebase/firestore@workspace:packages/firestore": version: 0.0.0-use.local resolution: "@react-native-firebase/firestore@workspace:packages/firestore" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/functions@npm:20.3.0, @react-native-firebase/functions@workspace:packages/functions": +"@react-native-firebase/functions@npm:20.4.0, @react-native-firebase/functions@workspace:packages/functions": version: 0.0.0-use.local resolution: "@react-native-firebase/functions@workspace:packages/functions" dependencies: "@react-native-firebase/private-tests-firebase-functions": "npm:^0.0.1" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/in-app-messaging@npm:20.3.0, @react-native-firebase/in-app-messaging@workspace:packages/in-app-messaging": +"@react-native-firebase/in-app-messaging@npm:20.4.0, @react-native-firebase/in-app-messaging@workspace:packages/in-app-messaging": version: 0.0.0-use.local resolution: "@react-native-firebase/in-app-messaging@workspace:packages/in-app-messaging" peerDependencies: - "@react-native-firebase/analytics": 20.3.0 - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/analytics": 20.4.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/installations@npm:20.3.0, @react-native-firebase/installations@workspace:packages/installations": +"@react-native-firebase/installations@npm:20.4.0, @react-native-firebase/installations@workspace:packages/installations": version: 0.0.0-use.local resolution: "@react-native-firebase/installations@workspace:packages/installations" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/messaging@npm:20.3.0, @react-native-firebase/messaging@workspace:packages/messaging": +"@react-native-firebase/messaging@npm:20.4.0, @react-native-firebase/messaging@workspace:packages/messaging": version: 0.0.0-use.local resolution: "@react-native-firebase/messaging@workspace:packages/messaging" dependencies: expo: "npm:^50.0.19" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 expo: ">=47.0.0" peerDependenciesMeta: expo: @@ -5960,21 +5960,21 @@ __metadata: languageName: unknown linkType: soft -"@react-native-firebase/ml@npm:20.3.0, @react-native-firebase/ml@workspace:packages/ml": +"@react-native-firebase/ml@npm:20.4.0, @react-native-firebase/ml@workspace:packages/ml": version: 0.0.0-use.local resolution: "@react-native-firebase/ml@workspace:packages/ml" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/perf@npm:20.3.0, @react-native-firebase/perf@workspace:packages/perf": +"@react-native-firebase/perf@npm:20.4.0, @react-native-firebase/perf@workspace:packages/perf": version: 0.0.0-use.local resolution: "@react-native-firebase/perf@workspace:packages/perf" dependencies: expo: "npm:^50.0.19" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 expo: ">=47.0.0" peerDependenciesMeta: expo: @@ -5989,20 +5989,20 @@ __metadata: languageName: node linkType: hard -"@react-native-firebase/remote-config@npm:20.3.0, @react-native-firebase/remote-config@workspace:packages/remote-config": +"@react-native-firebase/remote-config@npm:20.4.0, @react-native-firebase/remote-config@workspace:packages/remote-config": version: 0.0.0-use.local resolution: "@react-native-firebase/remote-config@workspace:packages/remote-config" peerDependencies: - "@react-native-firebase/analytics": 20.3.0 - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/analytics": 20.4.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft -"@react-native-firebase/storage@npm:20.3.0, @react-native-firebase/storage@workspace:packages/storage": +"@react-native-firebase/storage@npm:20.4.0, @react-native-firebase/storage@workspace:packages/storage": version: 0.0.0-use.local resolution: "@react-native-firebase/storage@workspace:packages/storage" peerDependencies: - "@react-native-firebase/app": 20.3.0 + "@react-native-firebase/app": 20.4.0 languageName: unknown linkType: soft @@ -19374,24 +19374,24 @@ __metadata: dependencies: "@firebase/rules-unit-testing": "npm:^3.0.3" "@react-native-async-storage/async-storage": "npm:^1.24.0" - "@react-native-firebase/analytics": "npm:20.3.0" - "@react-native-firebase/app": "npm:20.3.0" - "@react-native-firebase/app-check": "npm:20.3.0" - "@react-native-firebase/app-distribution": "npm:20.3.0" + "@react-native-firebase/analytics": "npm:20.4.0" + "@react-native-firebase/app": "npm:20.4.0" + "@react-native-firebase/app-check": "npm:20.4.0" + "@react-native-firebase/app-distribution": "npm:20.4.0" "@react-native-firebase/app-types": "npm:6.7.2" - "@react-native-firebase/auth": "npm:20.3.0" - "@react-native-firebase/crashlytics": "npm:20.3.0" - "@react-native-firebase/database": "npm:20.3.0" - "@react-native-firebase/dynamic-links": "npm:20.3.0" - "@react-native-firebase/firestore": "npm:20.3.0" - "@react-native-firebase/functions": "npm:20.3.0" - "@react-native-firebase/in-app-messaging": "npm:20.3.0" - "@react-native-firebase/installations": "npm:20.3.0" - "@react-native-firebase/messaging": "npm:20.3.0" - "@react-native-firebase/ml": "npm:20.3.0" - "@react-native-firebase/perf": "npm:20.3.0" - "@react-native-firebase/remote-config": "npm:20.3.0" - "@react-native-firebase/storage": "npm:20.3.0" + "@react-native-firebase/auth": "npm:20.4.0" + "@react-native-firebase/crashlytics": "npm:20.4.0" + "@react-native-firebase/database": "npm:20.4.0" + "@react-native-firebase/dynamic-links": "npm:20.4.0" + "@react-native-firebase/firestore": "npm:20.4.0" + "@react-native-firebase/functions": "npm:20.4.0" + "@react-native-firebase/in-app-messaging": "npm:20.4.0" + "@react-native-firebase/installations": "npm:20.4.0" + "@react-native-firebase/messaging": "npm:20.4.0" + "@react-native-firebase/ml": "npm:20.4.0" + "@react-native-firebase/perf": "npm:20.4.0" + "@react-native-firebase/remote-config": "npm:20.4.0" + "@react-native-firebase/storage": "npm:20.4.0" "@react-native/babel-preset": "npm:^0.74.84" "@react-native/metro-config": "npm:^0.74.84" axios: "npm:^1.5.1" From eba92d884c30cbf76cda6ddea09d2f6df4795b5e Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Tue, 13 Aug 2024 14:34:15 +0100 Subject: [PATCH 4/5] ci: ensure repo labels matches FlutterFire + label automation (#7970) --- .github/ISSUE_TEMPLATE/Bug_report.md | 2 +- .github/ISSUE_TEMPLATE/Documentation_issue.md | 2 +- .github/ISSUE_TEMPLATE/Feature_request.md | 12 +++++++ .../Other_platforms_bug_report.yml | 4 +-- .github/workflows/issue-labels.yaml | 36 +++++++++++++++++++ .github/workflows/stale.yml | 6 ++-- 6 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 .github/workflows/issue-labels.yaml diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index eff1749d81..e178ae8537 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -3,7 +3,7 @@ name: "⚠️ Bug/Issue report - React Native" about: Please provide as much detail as possible. Issues may be closed if they do not follow the template. title: "[\U0001F41B] Bug Report Title - CHANGE ME " -labels: 'Help: Needs Triage, Impact: Bug' +labels: 'Help: Needs Triage, Needs Attention, type: bug' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/Documentation_issue.md b/.github/ISSUE_TEMPLATE/Documentation_issue.md index e349a2b827..8172dad534 100644 --- a/.github/ISSUE_TEMPLATE/Documentation_issue.md +++ b/.github/ISSUE_TEMPLATE/Documentation_issue.md @@ -2,7 +2,7 @@ name: "\U0001F4D6 Documentation Feedback" about: Report an issue with the documentation or suggest an improvement. title: "[\U0001F4DA] Documentation Issue Title - CHANGE ME " -labels: 'Help: Good First Issue, Type: Docs' +labels: 'Help: Good First Issue, type: documentation' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000000..c10eab3250 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,12 @@ +--- +name: "🚀Feature Request" +about: Make a feature request for React Native Firebase. +title: "\U0001F41B [PACKAGE_NAME_HERE] Your feature request title here" +labels: 'Needs Attention, type: enhancement' +assignees: '' + +--- + +## What feature would you like to see? + +Is there a missing a feature that is supported on an underlying SDK? Or is there a feature that you think would be useful to have in React Native Firebase? diff --git a/.github/ISSUE_TEMPLATE/Other_platforms_bug_report.yml b/.github/ISSUE_TEMPLATE/Other_platforms_bug_report.yml index 0f35596f10..9b88888d1d 100644 --- a/.github/ISSUE_TEMPLATE/Other_platforms_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Other_platforms_bug_report.yml @@ -2,8 +2,8 @@ name: '⚠️ Other Platforms - Bug/Issue report' title: "[\U0001F41B] Other Platforms Bug Report Title - CHANGE ME " labels: - 'Help: Needs Triage' - - 'Impact: Bug' - - 'Platform: Other' + - 'type: bug' + - 'platform: macOS (Other)' description: Create an issue specific to 'Other' platforms on React Native Firebase (not Android or iOS). body: - type: checkboxes diff --git a/.github/workflows/issue-labels.yaml b/.github/workflows/issue-labels.yaml new file mode 100644 index 0000000000..1569034f25 --- /dev/null +++ b/.github/workflows/issue-labels.yaml @@ -0,0 +1,36 @@ +name: Update labels on issues with OP response + +on: + issue_comment: + types: [created] + +jobs: + label-op-response: + runs-on: ubuntu-latest + steps: + - name: Check if the comment is from the OP + id: check-op + run: | + OP=${{ github.event.issue.user.login }} + COMMENTER=${{ github.event.comment.user.login }} + + if [ "$OP" = "$COMMENTER" ]; then + echo "op_comment=true" >> $GITHUB_ENV + else + echo "op_comment=false" >> $GITHUB_ENV + fi + + - name: Add 'Needs Attention' label if OP responded + if: env.op_comment == 'true' + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: 'Needs Attention' + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + - name: Remove 'blocked customer-response' label if OP responded + if: env.op_comment == 'true' + uses: actions-ecosystem/action-remove-labels@v1 + with: + labels: 'blocked: customer-response' + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d2854052a5..0f482ce093 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -30,9 +30,9 @@ jobs: If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 15 days until this gets closed automatically - exempt-issue-labels: 'Keep Open,help: good-first-issue,Workflow: Needs Review' - exempt-pr-labels: 'Keep Open,help: good-first-issue,Workflow: Needs Review' + exempt-issue-labels: 'Keep Open,Help: Good First Issue,Workflow: Needs Review' + exempt-pr-labels: 'Keep Open,Help: Good First Issue,Workflow: Needs Review' close-issue-reason: not_planned days-before-stale: 28 days-before-close: 15 - stale-issue-label: 'Type: Stale' + stale-issue-label: 'Stale' From f978d8665816e5ef7401b10433c2483c6f5fceb1 Mon Sep 17 00:00:00 2001 From: Shubh Porwal <83606943+shubh73@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:35:41 +0530 Subject: [PATCH 5/5] fix(docs): typos (#7965) * fix: `resource` typo * fix: `initialized` typo * fix: `initialize` typo * fix: `minimized` typo * chore: trigger cla-action --- docs/crashlytics/usage/index.md | 2 +- docs/database/usage/index.md | 2 +- docs/messaging/usage/index.md | 2 +- docs/migrating-to-v6.md | 2 +- docs/releases/v6.0.0.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/crashlytics/usage/index.md b/docs/crashlytics/usage/index.md index c67e2727b8..3a20d9ec06 100644 --- a/docs/crashlytics/usage/index.md +++ b/docs/crashlytics/usage/index.md @@ -120,7 +120,7 @@ export default function App() { try { if (users) { // An empty array is truthy, but not actually true. - // Therefore the array was never initialised. + // Therefore the array was never initialized. setUserCounts(userCounts.push(users.length)); } } catch (error) { diff --git a/docs/database/usage/index.md b/docs/database/usage/index.md index a1d4dc9bcc..de30be12aa 100644 --- a/docs/database/usage/index.md +++ b/docs/database/usage/index.md @@ -341,7 +341,7 @@ For example: import database, { firebase } from '@react-native-firebase/database'; // create a secondary app -const secondaryApp = await firebase.initalizeApp(credentials, config); +const secondaryApp = await firebase.initializeApp(credentials, config); // pass the secondary app instance to the database module const secondaryDatabase = database(secondaryApp); diff --git a/docs/messaging/usage/index.md b/docs/messaging/usage/index.md index 69ee55c67b..ebf826d087 100644 --- a/docs/messaging/usage/index.md +++ b/docs/messaging/usage/index.md @@ -115,7 +115,7 @@ scenarios, it is first important to establish the various states a device can be | State | Description | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Foreground** | When the application is open and in view. | -| **Background** | When the application is open, however in the background (minimised). This typically occurs when the user has pressed the "home" button on the device or has switched to another app via the app switcher. | +| **Background** | When the application is open, however in the background (minimized). This typically occurs when the user has pressed the "home" button on the device or has switched to another app via the app switcher. | | **Quit** | When the device is locked or application is not active or running. The user can quit an app by "swiping it away" via the app switcher UI on the device. | The user must have opened the app before messages can be received. If the user force quits the app from the device settings, it must be re-opened again before receiving messages. diff --git a/docs/migrating-to-v6.md b/docs/migrating-to-v6.md index 1f8309da9b..a277dea68e 100644 --- a/docs/migrating-to-v6.md +++ b/docs/migrating-to-v6.md @@ -450,7 +450,7 @@ How to migrate: If you use device-local notification APIs and user-visible notif - Module namespace has been renamed to `.remoteConfig()` from `.config()`. - All Remote Config values can now be accessed synchronously in JS, see `getValue(key: string): ConfigValue` & `getAll(): ConfigValues` below. - These replace all the original async methods: `getValue`, `getValues`, `getKeysByPrefix`. -- `setDefaultsFromResource` now returns a Promise that resolves when completed, this will reject with code `config/resouce_not_found` if the file could not be found. +- `setDefaultsFromResource` now returns a Promise that resolves when completed, this will reject with code `config/resource_not_found` if the file could not be found. - `setDefaultsFromResource` now expects a resource file name for Android to match iOS, formerly this required a resource id (something you would not have in RN as this was generated at build time by Android). - An example for both platforms can be found in the tests. - `enableDeveloperMode` has been removed, you can now use `setConfigSettings({ isDeveloperModeEnabled: boolean })` instead. diff --git a/docs/releases/v6.0.0.md b/docs/releases/v6.0.0.md index 2dfb84f2bd..0ff51ef1b8 100644 --- a/docs/releases/v6.0.0.md +++ b/docs/releases/v6.0.0.md @@ -268,7 +268,7 @@ The Remote Config API has had a significant API change as originally highlighted - [BREAKING] Module namespace has been renamed to `.remoteConfig()`, replace all usages of `firebase.config` with the new name. - [BREAKING] All Remote Config values can now be accessed synchronously in JS, see `getValue(key: string): ConfigValue` & `getAll(): ConfigValues` below - [BREAKING] These replace all the original async methods: `getValue`, `getValues`, `getKeysByPrefix` -- [BREAKING] `setDefaultsFromResource` now returns a Promise that resolves when completed, this will reject with code `config/resouce_not_found` if the file could not be found +- [BREAKING] `setDefaultsFromResource` now returns a Promise that resolves when completed, this will reject with code `config/resource_not_found` if the file could not be found - [BREAKING] `setDefaultsFromResource` now expects a resource file name for Android to match iOS, formerly this required a resource id (something you would not have in RN as this was generated at build time by Android) - And example for both platforms can be found in the tests. - [BREAKING] `enableDeveloperMode` has been removed, you can now use `setConfigSettings({ isDeveloperModeEnabled: boolean })` instead