@react-native-firebase/app 26.3.2 → 26.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/RNFBApp.podspec +1 -1
  3. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.java +31 -0
  4. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.java +150 -0
  5. package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.java +27 -3
  6. package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.java +3 -1
  7. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
  8. package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +3 -2
  9. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java +5 -3
  10. package/android/src/test/java/io/invertase/firebase/app/NativeRNFBTurboAppTest.java +26 -1
  11. package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.java +275 -0
  12. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterTest.java +1 -1
  13. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.java +108 -0
  14. package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.java +97 -0
  15. package/android/src/test/java/io/invertase/firebase/utils/NativeRNFBTurboUtilsTest.java +0 -2
  16. package/dist/module/common/Base64.js +13 -21
  17. package/dist/module/common/Base64.js.map +1 -1
  18. package/dist/module/common/index.js +17 -9
  19. package/dist/module/common/index.js.map +1 -1
  20. package/dist/module/internal/asyncStorage.js +7 -11
  21. package/dist/module/internal/asyncStorage.js.map +1 -1
  22. package/dist/module/internal/logger.js +44 -40
  23. package/dist/module/internal/logger.js.map +1 -1
  24. package/dist/module/internal/nativeModuleAndroidIos.js +2 -29
  25. package/dist/module/internal/nativeModuleAndroidIos.js.map +1 -1
  26. package/dist/module/internal/nativeModuleDebug.js +74 -0
  27. package/dist/module/internal/nativeModuleDebug.js.map +1 -0
  28. package/dist/module/internal/nativeModuleWeb.js +9 -29
  29. package/dist/module/internal/nativeModuleWeb.js.map +1 -1
  30. package/dist/module/internal/registry/app.js +1 -1
  31. package/dist/module/internal/registry/app.js.map +1 -1
  32. package/dist/module/internal/registry/modular.js +2 -2
  33. package/dist/module/internal/registry/modular.js.map +1 -1
  34. package/dist/module/internal/registry/nativeModule.js +3 -3
  35. package/dist/module/internal/registry/nativeModule.js.map +1 -1
  36. package/dist/module/internal/web/RNFBAppModule.js +16 -15
  37. package/dist/module/internal/web/RNFBAppModule.js.map +1 -1
  38. package/dist/module/internal/web/memidb/lib/FakeEventTarget.js +3 -1
  39. package/dist/module/internal/web/memidb/lib/FakeEventTarget.js.map +1 -1
  40. package/dist/module/version.js +1 -1
  41. package/dist/typescript/lib/common/Base64.d.ts.map +1 -1
  42. package/dist/typescript/lib/common/index.d.ts.map +1 -1
  43. package/dist/typescript/lib/internal/asyncStorage.d.ts.map +1 -1
  44. package/dist/typescript/lib/internal/logger.d.ts.map +1 -1
  45. package/dist/typescript/lib/internal/nativeModuleAndroidIos.d.ts.map +1 -1
  46. package/dist/typescript/lib/internal/nativeModuleDebug.d.ts +2 -0
  47. package/dist/typescript/lib/internal/nativeModuleDebug.d.ts.map +1 -0
  48. package/dist/typescript/lib/internal/nativeModuleWeb.d.ts.map +1 -1
  49. package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts +7 -7
  50. package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts.map +1 -1
  51. package/dist/typescript/lib/version.d.ts +1 -1
  52. package/firebase-schema.json +1 -1
  53. package/firebase_spm.rb +240 -95
  54. package/ios/RNFBApp/RNFBAppModule.mm +42 -16
  55. package/ios/RNFBApp/RNFBHandleMap.h +73 -0
  56. package/ios/RNFBApp/RNFBHandleMap.m +116 -0
  57. package/ios/RNFBApp/RNFBSharedUtils.m +3 -2
  58. package/ios/RNFBApp/RNFBUtilsModule.mm +2 -3
  59. package/ios/RNFBApp/RNFBVersion.m +1 -1
  60. package/ios/RNFBApp.xcodeproj/project.pbxproj +6 -0
  61. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/project.pbxproj +237 -0
  62. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAppUnitTests.xcscheme +69 -0
  63. package/ios/RNFBAppUnitTests/RNFBHandleMapTests.m +238 -0
  64. package/lib/common/Base64.ts +10 -16
  65. package/lib/common/index.ts +20 -9
  66. package/lib/internal/asyncStorage.ts +7 -11
  67. package/lib/internal/logger.ts +53 -43
  68. package/lib/internal/nativeModuleAndroidIos.ts +2 -40
  69. package/lib/internal/nativeModuleDebug.ts +97 -0
  70. package/lib/internal/nativeModuleWeb.ts +10 -40
  71. package/lib/internal/registry/app.ts +1 -1
  72. package/lib/internal/registry/modular.ts +2 -2
  73. package/lib/internal/registry/nativeModule.ts +3 -3
  74. package/lib/internal/web/RNFBAppModule.ts +16 -15
  75. package/lib/internal/web/memidb/lib/FakeEventTarget.js +3 -1
  76. package/lib/version.ts +1 -1
  77. package/package.json +3 -2
  78. package/plugin/build/ios/appDelegate.js +18 -8
  79. package/plugin/src/ios/appDelegate.ts +44 -14
package/CHANGELOG.md CHANGED
@@ -3,6 +3,51 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [26.4.0](https://github.com/invertase/react-native-firebase/compare/v26.3.3...v26.4.0) (2026-09-05)
7
+
8
+ ### Features
9
+
10
+ - **app:** add RNFBHandleMap for thread-safe handle registries ([d28edd5](https://github.com/invertase/react-native-firebase/commit/d28edd579281da523b626934e89a14dd38dd3ed8))
11
+
12
+ ### Bug Fixes
13
+
14
+ - **app, android:** clear stale custom auth domains ([ccde348](https://github.com/invertase/react-native-firebase/commit/ccde3483511efd1c54bcc4676bd02893f28e9014))
15
+ - **app, android:** discard tasks posted to shut-down transactional executors ([de98eaa](https://github.com/invertase/react-native-firebase/commit/de98eaa9ff0484eeb9e00ec99d5228067611a4f3))
16
+ - **app, expo:** add missing Firebase imports ([7d6f19c](https://github.com/invertase/react-native-firebase/commit/7d6f19c5ba513c5880dd401e225ce19d550e27df))
17
+ - **app, ios:** clear stale custom auth domains ([d747981](https://github.com/invertase/react-native-firebase/commit/d7479818e52f4b4df84bba53c53345942b80af85))
18
+ - **app:** handle empty Android Firebase JSON ([5c48832](https://github.com/invertase/react-native-firebase/commit/5c48832f70a62c1061448a723f1e6a193e09c74a))
19
+ - **app:** isolate native routing keys ([fd13a7a](https://github.com/invertase/react-native-firebase/commit/fd13a7adf84791ce1c1c4ce38a1728a86ae9c6c0))
20
+ - **app:** make debug native proxies behavior-safe ([6caff3c](https://github.com/invertase/react-native-firebase/commit/6caff3cfc706133d132a2762b355c65979631555))
21
+ - **app:** parse data URL payloads completely ([eb5d54e](https://github.com/invertase/react-native-firebase/commit/eb5d54e8bdbd6748e6c024b26f19bf63b764d5bb))
22
+ - **app:** preserve empty memory storage values ([41ac6bc](https://github.com/invertase/react-native-firebase/commit/41ac6bc2f1113a6412e23dd171752f6e5762c335))
23
+ - **app:** preserve memidb event listeners ([c8648a6](https://github.com/invertase/react-native-firebase/commit/c8648a622533afd2f2a94ed36c3acd57c611a2b2))
24
+ - **app:** preserve web Promise contracts ([36c586e](https://github.com/invertase/react-native-firebase/commit/36c586ea908a2faaebf1d40846497bde096a779c))
25
+ - **app:** reject FileReader startup failures ([08319ae](https://github.com/invertase/react-native-firebase/commit/08319aed8191ab823a506b3166c18b8ef02853ac))
26
+ - **app:** retain global logger configuration ([23aeb7b](https://github.com/invertase/react-native-firebase/commit/23aeb7bb5fe60f9421ba81b3f88c859dfd6d3d49))
27
+ - **app:** support arbitrary web storage keys ([c2c9f42](https://github.com/invertase/react-native-firebase/commit/c2c9f4261464bf56c3e6521f54448dbe4649392f))
28
+ - **app:** support prototype-named apps ([0ed7a0f](https://github.com/invertase/react-native-firebase/commit/0ed7a0fd7c99c894e2b94fd3de733dfa43096af2))
29
+ - **auth, ios:** look up customAuthDomains with JS app name ([9873f61](https://github.com/invertase/react-native-firebase/commit/9873f618a972c3324ec05b9e30f5aaefa7ab163b))
30
+ - **database:** use RNFBHandleMap for transaction and query maps ([8adba4c](https://github.com/invertase/react-native-firebase/commit/8adba4cb88dffec2442ea0fe37738d1080d0a185))
31
+ - **messaging, android:** support disabled notification storage ([afaba0b](https://github.com/invertase/react-native-firebase/commit/afaba0b3862b65a2bd83bbbfebfae3ac576811fd))
32
+
33
+ ## [26.3.3](https://github.com/invertase/react-native-firebase/compare/v26.3.2...v26.3.3) (2026-09-01)
34
+
35
+ ### Bug Fixes
36
+
37
+ - **e2e:** ready-gate Firebase emulator startup ([9ca2aec](https://github.com/invertase/react-native-firebase/commit/9ca2aecc2aa9140c38749d68ab3c23ac23ca8f46))
38
+ - **e2e:** restart Metro with a status gate ([4846e4e](https://github.com/invertase/react-native-firebase/commit/4846e4e345791cd95d7f7952c84a7c229a1f59aa))
39
+ - **ios:** align TurboModule constants return type with generated spec ([c79e1d2](https://github.com/invertase/react-native-firebase/commit/c79e1d2bc43b00a6ef61d38a294feceaed447740)), closes [#9212](https://github.com/invertase/react-native-firebase/issues/9212)
40
+ - **ios:** harden Expo linkage restore diagnostics ([c6e960b](https://github.com/invertase/react-native-firebase/commit/c6e960ba47e881a848e93d60cf09ea2064b24d1b))
41
+ - **ios:** link FirebaseCore into Expo app target under SPM ([18f1d46](https://github.com/invertase/react-native-firebase/commit/18f1d4645ef2b59c03436cf7fa68e6ad39ba9805))
42
+ - **ios:** persist Pods explicit-modules off after post_integrate ([54b3a4a](https://github.com/invertase/react-native-firebase/commit/54b3a4a9913d4601cc5562f559e8b9c8e799e105))
43
+ - **ios:** point SPM Podfile fallbacks at post_integrate ([21191ac](https://github.com/invertase/react-native-firebase/commit/21191acd767b045150dd40af07192d6c735c918e))
44
+ - **ios:** restore RNFB dynamic linkage after Expo prebuilt downgrade ([1d6d80d](https://github.com/invertase/react-native-firebase/commit/1d6d80de01592bd2f38a4270ab268fa40d27a16a))
45
+ - **ios:** warn when Expo linkage restore cannot run ([fecb129](https://github.com/invertase/react-native-firebase/commit/fecb129418066ae035ae2a271cf11466e508db54))
46
+
47
+ ### Reverts
48
+
49
+ - Revert "chore(release): release packages" ([0a85268](https://github.com/invertase/react-native-firebase/commit/0a85268ae21c706cf1392eaa5b088e85c3273cb7))
50
+
6
51
  ## [26.3.2](https://github.com/invertase/react-native-firebase/compare/v26.3.1...v26.3.2) (2026-08-21)
7
52
 
8
53
  **Note:** Version bump only for package @react-native-firebase/app
package/RNFBApp.podspec CHANGED
@@ -25,7 +25,7 @@ Pod::Spec.new do |s|
25
25
  s.cocoapods_version = '>= 1.12.0'
26
26
  s.source_files = "ios/**/*.{h,m,mm,cpp}"
27
27
  s.private_header_files = "ios/**/*.h"
28
- s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*'
28
+ s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*', 'ios/*UnitTests/**'
29
29
 
30
30
  # Fail fast for old architecture users, but safely in case the variable goes away
31
31
  # completely in future react-native versions
@@ -0,0 +1,31 @@
1
+ package io.invertase.firebase.common;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ /**
21
+ * Thrown when {@link RNFBHandleMap#put} is asked to register an id that is already occupied.
22
+ *
23
+ * <p>This is a checked exception ({@code extends Exception}, not {@link RuntimeException}) so
24
+ * callers must handle collisions explicitly. There is no upsert: replace an existing registration
25
+ * with {@code take} then {@code put}.
26
+ */
27
+ public class RNFBHandleCollisionException extends Exception {
28
+ public RNFBHandleCollisionException(Object id) {
29
+ super("Handle id already registered: " + id);
30
+ }
31
+ }
@@ -0,0 +1,150 @@
1
+ package io.invertase.firebase.common;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import java.util.ArrayList;
21
+ import java.util.HashMap;
22
+ import java.util.List;
23
+ import java.util.function.Predicate;
24
+
25
+ /**
26
+ * Thread-safe id → handle registry. The lock only moves pointers; this class never invokes methods
27
+ * on stored values (no SDK {@code cancel}/{@code remove}). Callers {@link #take} or {@link
28
+ * #takeAll}, then act on the returned object(s) outside the lock.
29
+ *
30
+ * <p>{@link #put} is unique: an occupied id throws {@link RNFBHandleCollisionException}. There is
31
+ * no upsert. Use {@link #putIfAbsent} (first wins), {@link #putIfAbsentOrSame} (first wins or same
32
+ * instance), or {@link #putReplacing} (last wins) for atomic upsert. Replace at the call site with
33
+ * {@code take} then {@code put} only when neither helper fits.
34
+ *
35
+ * <p><strong>Do not lock on the map or on a {@code RNFBHandleMap} instance.</strong> Callers that
36
+ * synchronize on {@code this} (or on the internal map) can deadlock with the dedicated monitor.
37
+ * Android uses a private {@code lock} object; never expose it and never {@code synchronized (this)}
38
+ * around these methods.
39
+ */
40
+ public class RNFBHandleMap<K, V> {
41
+ private final Object lock = new Object();
42
+ private final HashMap<K, V> map = new HashMap<>();
43
+
44
+ /**
45
+ * Registers {@code handle} under {@code id}.
46
+ *
47
+ * @throws RNFBHandleCollisionException if {@code id} is already occupied
48
+ */
49
+ public void put(K id, V handle) throws RNFBHandleCollisionException {
50
+ synchronized (lock) {
51
+ if (map.containsKey(id)) {
52
+ throw new RNFBHandleCollisionException(id);
53
+ }
54
+ map.put(id, handle);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Stores {@code handle} under {@code id} only when the id is free (first wins).
60
+ *
61
+ * @return {@code true} if stored, {@code false} if {@code id} was already occupied
62
+ */
63
+ public boolean putIfAbsent(K id, V handle) {
64
+ synchronized (lock) {
65
+ if (map.containsKey(id)) {
66
+ return false;
67
+ }
68
+ map.put(id, handle);
69
+ return true;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Under one lock: stores {@code handle} when the id is free, or returns {@code true} when the
75
+ * existing mapping is the same instance ({@code ==}). Returns {@code false} when occupied by a
76
+ * different handle.
77
+ */
78
+ public boolean putIfAbsentOrSame(K id, V handle) {
79
+ synchronized (lock) {
80
+ V existing = map.get(id);
81
+ if (existing == null) {
82
+ map.put(id, handle);
83
+ return true;
84
+ }
85
+ return existing == handle;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Atomically replaces the mapping for {@code id} (last wins). Returns the previous handle, or
91
+ * {@code null} if the id was free.
92
+ */
93
+ public V putReplacing(K id, V handle) {
94
+ synchronized (lock) {
95
+ return map.put(id, handle);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Peeks at the handle for {@code id} without removing it (for emit while still registered).
101
+ *
102
+ * @return the handle, or {@code null} if absent
103
+ */
104
+ public V get(K id) {
105
+ synchronized (lock) {
106
+ return map.get(id);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Removes and returns the handle for {@code id}.
112
+ *
113
+ * @return the handle, or {@code null} if absent
114
+ */
115
+ public V take(K id) {
116
+ synchronized (lock) {
117
+ return map.remove(id);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * When {@code id} is mapped and {@code shouldTake} accepts the handle, removes and returns it;
123
+ * otherwise returns {@code null} and leaves the map unchanged. Lookup, predicate, and removal run
124
+ * under one lock — use for check-and-take that must not race with concurrent {@link #put} /
125
+ * {@link #take} on the same id.
126
+ *
127
+ * <p>The predicate runs under this map's lock; keep it lightweight (no SDK cancel/remove).
128
+ */
129
+ public V takeIf(K id, Predicate<V> shouldTake) {
130
+ synchronized (lock) {
131
+ V value = map.get(id);
132
+ if (value != null && shouldTake.test(value)) {
133
+ return map.remove(id);
134
+ }
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Snapshot of current values, then clear. Cancel/remove each returned object after this method
141
+ * returns, not under this map's lock.
142
+ */
143
+ public List<V> takeAll() {
144
+ synchronized (lock) {
145
+ List<V> values = new ArrayList<>(map.values());
146
+ map.clear();
147
+ return values;
148
+ }
149
+ }
150
+ }
@@ -22,7 +22,8 @@ import java.util.HashMap;
22
22
  import java.util.List;
23
23
  import java.util.Map;
24
24
  import java.util.concurrent.ExecutorService;
25
- import java.util.concurrent.Executors;
25
+ import java.util.concurrent.LinkedBlockingQueue;
26
+ import java.util.concurrent.RejectedExecutionException;
26
27
  import java.util.concurrent.RejectedExecutionHandler;
27
28
  import java.util.concurrent.SynchronousQueue;
28
29
  import java.util.concurrent.ThreadPoolExecutor;
@@ -73,7 +74,16 @@ public class TaskExecutorService {
73
74
 
74
75
  private ExecutorService getNewExecutor(boolean isTransactional) {
75
76
  if (isTransactional) {
76
- return Executors.newSingleThreadExecutor();
77
+ // A bare Executors.newSingleThreadExecutor() has no rejection handler, so a
78
+ // play-services Task completing after shutdown() (module invalidation racing
79
+ // in-flight work, e.g. App Check attestation or Auth network calls finishing
80
+ // while the React instance is torn down) posts to the dead executor and
81
+ // crashes the process with an uncaught RejectedExecutionException. Use an
82
+ // equivalent single-thread pool that discards work arriving after shutdown.
83
+ ThreadPoolExecutor transactionalExecutor =
84
+ new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
85
+ transactionalExecutor.setRejectedExecutionHandler(discardAfterShutdown);
86
+ return transactionalExecutor;
77
87
  } else {
78
88
  ThreadPoolExecutor threadPoolExecutor =
79
89
  new ThreadPoolExecutor(
@@ -83,9 +93,23 @@ public class TaskExecutorService {
83
93
  }
84
94
  }
85
95
 
96
+ private static final RejectedExecutionHandler discardAfterShutdown =
97
+ (r, executor) -> {
98
+ // isShutdown() remains true through the terminating and terminated states,
99
+ // so it is the only check needed to detect work arriving after shutdown.
100
+ if (executor.isShutdown()) {
101
+ return;
102
+ }
103
+ // Unreachable with an unbounded queue, but preserve AbortPolicy semantics
104
+ // for any rejection that is not caused by shutdown.
105
+ throw new RejectedExecutionException("Task " + r + " rejected from " + executor);
106
+ };
107
+
86
108
  private final RejectedExecutionHandler executeInFallback =
87
109
  (r, executor) -> {
88
- if (executor.isShutdown() || executor.isTerminated() || executor.isTerminating()) {
110
+ // isShutdown() remains true through the terminating and terminated states,
111
+ // so it is the only check needed to detect work arriving after shutdown.
112
+ if (executor.isShutdown()) {
89
113
  return;
90
114
  }
91
115
  ExecutorService fallbackExecutor = getTransactionalExecutor();
@@ -35,11 +35,12 @@ import java.util.ArrayList;
35
35
  import java.util.HashMap;
36
36
  import java.util.List;
37
37
  import java.util.Map;
38
+ import java.util.concurrent.ConcurrentHashMap;
38
39
 
39
40
  public class NativeRNFBTurboApp extends NativeRNFBTurboAppSpec implements LifecycleEventListener {
40
41
  private static final String TAG = "App";
41
42
 
42
- public static Map<String, String> authDomains = new HashMap<>();
43
+ public static final Map<String, String> authDomains = new ConcurrentHashMap<>();
43
44
 
44
45
  NativeRNFBTurboApp(ReactApplicationContext reactContext) {
45
46
  super(reactContext);
@@ -134,6 +135,7 @@ public class NativeRNFBTurboApp extends NativeRNFBTurboAppSpec implements Lifecy
134
135
 
135
136
  if (firebaseApp != null) {
136
137
  firebaseApp.delete();
138
+ authDomains.remove(appName);
137
139
  }
138
140
 
139
141
  promise.resolve(null);
@@ -18,5 +18,5 @@ package io.invertase.firebase.app;
18
18
  */
19
19
  // generated file - do not modify or commit
20
20
  public class ReactNativeFirebaseVersion {
21
- public static String VERSION = "26.3.2";
21
+ public static String VERSION = "26.4.0";
22
22
  }
@@ -57,8 +57,9 @@ public class RCTConvertFirebase {
57
57
  options.put("messagingSenderId", appOptions.getGcmSenderId());
58
58
  options.put("storageBucket", appOptions.getStorageBucket());
59
59
 
60
- if (NativeRNFBTurboApp.authDomains.get(name) != null) {
61
- options.put("authDomain", NativeRNFBTurboApp.authDomains.get(name));
60
+ String authDomain = NativeRNFBTurboApp.authDomains.get(name);
61
+ if (authDomain != null) {
62
+ options.put("authDomain", authDomain);
62
63
  }
63
64
 
64
65
  root.put("options", options);
@@ -21,6 +21,7 @@ import com.facebook.react.bridge.Arguments;
21
21
  import com.facebook.react.bridge.WritableMap;
22
22
  import io.invertase.firebase.BuildConfig;
23
23
  import java.util.ArrayList;
24
+ import java.util.Iterator;
24
25
  import org.json.JSONArray;
25
26
  import org.json.JSONException;
26
27
  import org.json.JSONObject;
@@ -91,11 +92,12 @@ public class ReactNativeFirebaseJSON {
91
92
 
92
93
  public WritableMap getAll() {
93
94
  WritableMap writableMap = Arguments.createMap();
95
+ if (jsonObject == null) return writableMap;
94
96
 
95
- JSONArray keys = jsonObject.names();
96
- for (int i = 0; i < keys.length(); ++i) {
97
+ Iterator<String> keys = jsonObject.keys();
98
+ while (keys.hasNext()) {
97
99
  try {
98
- String key = keys.getString(i);
100
+ String key = keys.next();
99
101
  SharedUtils.mapPutValue(key, jsonObject.get(key), writableMap);
100
102
  } catch (JSONException e) {
101
103
  // ignore
@@ -37,6 +37,7 @@ import com.facebook.react.bridge.ReactContext;
37
37
  import com.facebook.react.bridge.ReadableMap;
38
38
  import com.facebook.react.bridge.WritableMap;
39
39
  import com.google.firebase.FirebaseApp;
40
+ import com.google.firebase.FirebaseOptions;
40
41
  import io.invertase.firebase.common.RCTConvertFirebase;
41
42
  import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
42
43
  import io.invertase.firebase.common.ReactNativeFirebaseJSON;
@@ -63,7 +64,7 @@ import org.robolectric.shadows.ShadowLooper;
63
64
  * preferences bridges.
64
65
  */
65
66
  @RunWith(RobolectricTestRunner.class)
66
- @Config(sdk = 34, application = NativeRNFBTurboAppTest.HostApplication.class)
67
+ @Config(application = NativeRNFBTurboAppTest.HostApplication.class)
67
68
  public class NativeRNFBTurboAppTest {
68
69
 
69
70
  public static class HostApplication extends Application implements ReactApplication {
@@ -206,6 +207,28 @@ public class NativeRNFBTurboAppTest {
206
207
  assertFalse(NativeRNFBTurboApp.authDomains.containsKey("secondary"));
207
208
  }
208
209
 
210
+ @Test
211
+ public void firebaseAppToMap_includesOnlyConfiguredAuthDomain() {
212
+ FirebaseApp firebaseApp = mock(FirebaseApp.class);
213
+ FirebaseOptions options = mock(FirebaseOptions.class);
214
+ when(firebaseApp.getName()).thenReturn("secondary");
215
+ when(firebaseApp.getOptions()).thenReturn(options);
216
+
217
+ NativeRNFBTurboApp.configureAuthDomain("secondary", "example.firebaseapp.com");
218
+ Map<String, Object> appWithAuthDomain = RCTConvertFirebase.firebaseAppToMap(firebaseApp);
219
+ @SuppressWarnings("unchecked")
220
+ Map<String, Object> optionsWithAuthDomain =
221
+ (Map<String, Object>) appWithAuthDomain.get("options");
222
+ assertEquals("example.firebaseapp.com", optionsWithAuthDomain.get("authDomain"));
223
+
224
+ NativeRNFBTurboApp.configureAuthDomain("secondary", null);
225
+ Map<String, Object> appWithoutAuthDomain = RCTConvertFirebase.firebaseAppToMap(firebaseApp);
226
+ @SuppressWarnings("unchecked")
227
+ Map<String, Object> optionsWithoutAuthDomain =
228
+ (Map<String, Object>) appWithoutAuthDomain.get("options");
229
+ assertFalse(optionsWithoutAuthDomain.containsKey("authDomain"));
230
+ }
231
+
209
232
  @Test
210
233
  public void setAutomaticDataCollectionEnabled_delegatesToFirebaseApp() {
211
234
  FirebaseApp firebaseApp = mock(FirebaseApp.class);
@@ -223,6 +246,7 @@ public class NativeRNFBTurboAppTest {
223
246
  public void deleteApp_deletesWhenInstancePresent() {
224
247
  FirebaseApp firebaseApp = mock(FirebaseApp.class);
225
248
  Promise promise = mock(Promise.class);
249
+ NativeRNFBTurboApp.configureAuthDomain("secondary", "example.firebaseapp.com");
226
250
  try (MockedStatic<FirebaseApp> firebaseApps = mockStatic(FirebaseApp.class)) {
227
251
  firebaseApps.when(() -> FirebaseApp.getInstance("secondary")).thenReturn(firebaseApp);
228
252
 
@@ -230,6 +254,7 @@ public class NativeRNFBTurboAppTest {
230
254
  module.deleteApp("secondary", promise);
231
255
 
232
256
  verify(firebaseApp).delete();
257
+ assertFalse(NativeRNFBTurboApp.authDomains.containsKey("secondary"));
233
258
  verify(promise).resolve(null);
234
259
  }
235
260
  }