@react-native-firebase/app 26.3.3 → 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.
- package/CHANGELOG.md +27 -0
- package/RNFBApp.podspec +1 -1
- package/android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.java +31 -0
- package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.java +150 -0
- package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.java +27 -3
- package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.java +3 -1
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
- package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +3 -2
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java +5 -3
- package/android/src/test/java/io/invertase/firebase/app/NativeRNFBTurboAppTest.java +26 -1
- package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.java +275 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterTest.java +1 -1
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.java +108 -0
- package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.java +97 -0
- package/android/src/test/java/io/invertase/firebase/utils/NativeRNFBTurboUtilsTest.java +0 -2
- package/dist/module/common/Base64.js +13 -21
- package/dist/module/common/Base64.js.map +1 -1
- package/dist/module/common/index.js +17 -9
- package/dist/module/common/index.js.map +1 -1
- package/dist/module/internal/asyncStorage.js +7 -11
- package/dist/module/internal/asyncStorage.js.map +1 -1
- package/dist/module/internal/logger.js +44 -40
- package/dist/module/internal/logger.js.map +1 -1
- package/dist/module/internal/nativeModuleAndroidIos.js +2 -29
- package/dist/module/internal/nativeModuleAndroidIos.js.map +1 -1
- package/dist/module/internal/nativeModuleDebug.js +74 -0
- package/dist/module/internal/nativeModuleDebug.js.map +1 -0
- package/dist/module/internal/nativeModuleWeb.js +9 -29
- package/dist/module/internal/nativeModuleWeb.js.map +1 -1
- package/dist/module/internal/registry/app.js +1 -1
- package/dist/module/internal/registry/app.js.map +1 -1
- package/dist/module/internal/registry/modular.js +2 -2
- package/dist/module/internal/registry/modular.js.map +1 -1
- package/dist/module/internal/registry/nativeModule.js +3 -3
- package/dist/module/internal/registry/nativeModule.js.map +1 -1
- package/dist/module/internal/web/RNFBAppModule.js +16 -15
- package/dist/module/internal/web/RNFBAppModule.js.map +1 -1
- package/dist/module/internal/web/memidb/lib/FakeEventTarget.js +3 -1
- package/dist/module/internal/web/memidb/lib/FakeEventTarget.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/common/Base64.d.ts.map +1 -1
- package/dist/typescript/lib/common/index.d.ts.map +1 -1
- package/dist/typescript/lib/internal/asyncStorage.d.ts.map +1 -1
- package/dist/typescript/lib/internal/logger.d.ts.map +1 -1
- package/dist/typescript/lib/internal/nativeModuleAndroidIos.d.ts.map +1 -1
- package/dist/typescript/lib/internal/nativeModuleDebug.d.ts +2 -0
- package/dist/typescript/lib/internal/nativeModuleDebug.d.ts.map +1 -0
- package/dist/typescript/lib/internal/nativeModuleWeb.d.ts.map +1 -1
- package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts +7 -7
- package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts.map +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/firebase-schema.json +1 -1
- package/ios/RNFBApp/RNFBAppModule.mm +40 -14
- package/ios/RNFBApp/RNFBHandleMap.h +73 -0
- package/ios/RNFBApp/RNFBHandleMap.m +116 -0
- package/ios/RNFBApp/RNFBSharedUtils.m +3 -2
- package/ios/RNFBApp/RNFBVersion.m +1 -1
- package/ios/RNFBApp.xcodeproj/project.pbxproj +6 -0
- package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/project.pbxproj +237 -0
- package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAppUnitTests.xcscheme +69 -0
- package/ios/RNFBAppUnitTests/RNFBHandleMapTests.m +238 -0
- package/lib/common/Base64.ts +10 -16
- package/lib/common/index.ts +20 -9
- package/lib/internal/asyncStorage.ts +7 -11
- package/lib/internal/logger.ts +53 -43
- package/lib/internal/nativeModuleAndroidIos.ts +2 -40
- package/lib/internal/nativeModuleDebug.ts +97 -0
- package/lib/internal/nativeModuleWeb.ts +10 -40
- package/lib/internal/registry/app.ts +1 -1
- package/lib/internal/registry/modular.ts +2 -2
- package/lib/internal/registry/nativeModule.ts +3 -3
- package/lib/internal/web/RNFBAppModule.ts +16 -15
- package/lib/internal/web/memidb/lib/FakeEventTarget.js +3 -1
- package/lib/version.ts +1 -1
- package/package.json +3 -2
- package/plugin/build/ios/appDelegate.js +18 -8
- package/plugin/src/ios/appDelegate.ts +44 -14
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,33 @@
|
|
|
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
|
+
|
|
6
33
|
## [26.3.3](https://github.com/invertase/react-native-firebase/compare/v26.3.2...v26.3.3) (2026-09-01)
|
|
7
34
|
|
|
8
35
|
### Bug Fixes
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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);
|
|
@@ -57,8 +57,9 @@ public class RCTConvertFirebase {
|
|
|
57
57
|
options.put("messagingSenderId", appOptions.getGcmSenderId());
|
|
58
58
|
options.put("storageBucket", appOptions.getStorageBucket());
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
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);
|
package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java
CHANGED
|
@@ -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
|
-
|
|
96
|
-
|
|
97
|
+
Iterator<String> keys = jsonObject.keys();
|
|
98
|
+
while (keys.hasNext()) {
|
|
97
99
|
try {
|
|
98
|
-
String key = keys.
|
|
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(
|
|
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
|
}
|
|
@@ -0,0 +1,275 @@
|
|
|
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 static org.junit.Assert.assertEquals;
|
|
21
|
+
import static org.junit.Assert.assertFalse;
|
|
22
|
+
import static org.junit.Assert.assertNull;
|
|
23
|
+
import static org.junit.Assert.assertSame;
|
|
24
|
+
import static org.junit.Assert.assertTrue;
|
|
25
|
+
import static org.junit.Assert.fail;
|
|
26
|
+
|
|
27
|
+
import java.util.List;
|
|
28
|
+
import java.util.concurrent.CountDownLatch;
|
|
29
|
+
import java.util.concurrent.TimeUnit;
|
|
30
|
+
import java.util.concurrent.atomic.AtomicReference;
|
|
31
|
+
import org.junit.Before;
|
|
32
|
+
import org.junit.Test;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* JVM coverage for {@link RNFBHandleMap}. Tests must not invoke methods on stored values — the map
|
|
36
|
+
* only moves pointers.
|
|
37
|
+
*/
|
|
38
|
+
public class RNFBHandleMapTest {
|
|
39
|
+
|
|
40
|
+
private RNFBHandleMap<Integer, Object> map;
|
|
41
|
+
|
|
42
|
+
@Before
|
|
43
|
+
public void setUp() {
|
|
44
|
+
map = new RNFBHandleMap<>();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Test
|
|
48
|
+
public void putGetTake_happyPath() throws Exception {
|
|
49
|
+
Object handle = new Object();
|
|
50
|
+
map.put(1, handle);
|
|
51
|
+
|
|
52
|
+
assertSame(handle, map.get(1));
|
|
53
|
+
assertSame(handle, map.take(1));
|
|
54
|
+
assertNull(map.get(1));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test
|
|
58
|
+
public void take_missingKey_returnsNull() {
|
|
59
|
+
assertNull(map.take(99));
|
|
60
|
+
assertNull(map.get(99));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
public void takeAll_returnsSnapshotAndLeavesMapEmpty() throws Exception {
|
|
65
|
+
Object a = new Object();
|
|
66
|
+
Object b = new Object();
|
|
67
|
+
map.put(1, a);
|
|
68
|
+
map.put(2, b);
|
|
69
|
+
|
|
70
|
+
List<Object> snapshot = map.takeAll();
|
|
71
|
+
|
|
72
|
+
assertEquals(2, snapshot.size());
|
|
73
|
+
assertTrue(snapshot.contains(a));
|
|
74
|
+
assertTrue(snapshot.contains(b));
|
|
75
|
+
assertNull(map.get(1));
|
|
76
|
+
assertNull(map.get(2));
|
|
77
|
+
assertTrue(map.takeAll().isEmpty());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Test
|
|
81
|
+
public void put_occupiedId_throwsCollision() throws Exception {
|
|
82
|
+
Object first = new Object();
|
|
83
|
+
map.put(1, first);
|
|
84
|
+
try {
|
|
85
|
+
map.put(1, new Object());
|
|
86
|
+
fail("expected RNFBHandleCollisionException");
|
|
87
|
+
} catch (RNFBHandleCollisionException e) {
|
|
88
|
+
assertTrue(e.getMessage().contains("1"));
|
|
89
|
+
assertSame(first, map.get(1));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Test
|
|
94
|
+
public void putIfAbsent_whenFree_storesAndReturnsTrue() throws Exception {
|
|
95
|
+
Object handle = new Object();
|
|
96
|
+
assertTrue(map.putIfAbsent(1, handle));
|
|
97
|
+
assertSame(handle, map.get(1));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test
|
|
101
|
+
public void putIfAbsent_whenOccupied_keepsExistingAndReturnsFalse() throws Exception {
|
|
102
|
+
Object first = new Object();
|
|
103
|
+
Object second = new Object();
|
|
104
|
+
map.put(1, first);
|
|
105
|
+
assertFalse(map.putIfAbsent(1, second));
|
|
106
|
+
assertSame(first, map.get(1));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Test
|
|
110
|
+
public void putIfAbsentOrSame_whenAbsent_storesAndReturnsTrue() {
|
|
111
|
+
Object handle = new Object();
|
|
112
|
+
assertTrue(map.putIfAbsentOrSame(1, handle));
|
|
113
|
+
assertSame(handle, map.get(1));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Test
|
|
117
|
+
public void putIfAbsentOrSame_whenSameInstance_returnsTrue() throws Exception {
|
|
118
|
+
Object handle = new Object();
|
|
119
|
+
map.put(1, handle);
|
|
120
|
+
assertTrue(map.putIfAbsentOrSame(1, handle));
|
|
121
|
+
assertSame(handle, map.get(1));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@Test
|
|
125
|
+
public void putIfAbsentOrSame_whenDifferent_returnsFalse() throws Exception {
|
|
126
|
+
Object first = new Object();
|
|
127
|
+
Object second = new Object();
|
|
128
|
+
map.put(1, first);
|
|
129
|
+
assertFalse(map.putIfAbsentOrSame(1, second));
|
|
130
|
+
assertSame(first, map.get(1));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
public void putReplacing_whenFree_storesAndReturnsNull() throws Exception {
|
|
135
|
+
Object handle = new Object();
|
|
136
|
+
assertNull(map.putReplacing(1, handle));
|
|
137
|
+
assertSame(handle, map.get(1));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Test
|
|
141
|
+
public void putReplacing_whenOccupied_replacesLastWins() throws Exception {
|
|
142
|
+
Object first = new Object();
|
|
143
|
+
Object second = new Object();
|
|
144
|
+
map.put(1, first);
|
|
145
|
+
assertSame(first, map.putReplacing(1, second));
|
|
146
|
+
assertSame(second, map.get(1));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Test
|
|
150
|
+
public void put_afterTake_allowsReuse() throws Exception {
|
|
151
|
+
Object first = new Object();
|
|
152
|
+
Object second = new Object();
|
|
153
|
+
map.put(1, first);
|
|
154
|
+
assertSame(first, map.take(1));
|
|
155
|
+
map.put(1, second);
|
|
156
|
+
assertSame(second, map.get(1));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Test
|
|
160
|
+
public void takeIf_whenPredicateTrue_removesAndReturns() throws Exception {
|
|
161
|
+
Object handle = new Object();
|
|
162
|
+
map.put(1, handle);
|
|
163
|
+
assertSame(handle, map.takeIf(1, v -> v == handle));
|
|
164
|
+
assertNull(map.get(1));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@Test
|
|
168
|
+
public void takeIf_whenPredicateFalse_leavesMapping() throws Exception {
|
|
169
|
+
Object handle = new Object();
|
|
170
|
+
map.put(1, handle);
|
|
171
|
+
assertNull(map.takeIf(1, v -> false));
|
|
172
|
+
assertSame(handle, map.get(1));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@Test
|
|
176
|
+
public void takeIf_whenMissing_returnsNull() {
|
|
177
|
+
assertNull(map.takeIf(99, v -> true));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@Test
|
|
181
|
+
public void take_sameIdFromTwoThreads_onlyOneReturnsNonNull() throws Exception {
|
|
182
|
+
Object handle = new Object();
|
|
183
|
+
map.put(1, handle);
|
|
184
|
+
|
|
185
|
+
CountDownLatch start = new CountDownLatch(1);
|
|
186
|
+
CountDownLatch done = new CountDownLatch(2);
|
|
187
|
+
AtomicReference<Object> first = new AtomicReference<>();
|
|
188
|
+
AtomicReference<Object> second = new AtomicReference<>();
|
|
189
|
+
|
|
190
|
+
Thread t1 =
|
|
191
|
+
new Thread(
|
|
192
|
+
() -> {
|
|
193
|
+
try {
|
|
194
|
+
start.await();
|
|
195
|
+
first.set(map.take(1));
|
|
196
|
+
} catch (InterruptedException e) {
|
|
197
|
+
Thread.currentThread().interrupt();
|
|
198
|
+
} finally {
|
|
199
|
+
done.countDown();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
Thread t2 =
|
|
203
|
+
new Thread(
|
|
204
|
+
() -> {
|
|
205
|
+
try {
|
|
206
|
+
start.await();
|
|
207
|
+
second.set(map.take(1));
|
|
208
|
+
} catch (InterruptedException e) {
|
|
209
|
+
Thread.currentThread().interrupt();
|
|
210
|
+
} finally {
|
|
211
|
+
done.countDown();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
t1.start();
|
|
216
|
+
t2.start();
|
|
217
|
+
start.countDown();
|
|
218
|
+
assertTrue(done.await(5, TimeUnit.SECONDS));
|
|
219
|
+
|
|
220
|
+
Object a = first.get();
|
|
221
|
+
Object b = second.get();
|
|
222
|
+
boolean onlyFirst = a == handle && b == null;
|
|
223
|
+
boolean onlySecond = b == handle && a == null;
|
|
224
|
+
assertTrue(onlyFirst || onlySecond);
|
|
225
|
+
assertNull(map.get(1));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Test
|
|
229
|
+
public void putReplacing_sameIdFromTwoThreads_lastWins() throws Exception {
|
|
230
|
+
Object first = new Object();
|
|
231
|
+
Object second = new Object();
|
|
232
|
+
Object third = new Object();
|
|
233
|
+
map.put(1, first);
|
|
234
|
+
|
|
235
|
+
CountDownLatch start = new CountDownLatch(1);
|
|
236
|
+
CountDownLatch done = new CountDownLatch(2);
|
|
237
|
+
AtomicReference<Object> prevA = new AtomicReference<>();
|
|
238
|
+
AtomicReference<Object> prevB = new AtomicReference<>();
|
|
239
|
+
|
|
240
|
+
Thread t1 =
|
|
241
|
+
new Thread(
|
|
242
|
+
() -> {
|
|
243
|
+
try {
|
|
244
|
+
start.await();
|
|
245
|
+
prevA.set(map.putReplacing(1, second));
|
|
246
|
+
} catch (InterruptedException e) {
|
|
247
|
+
Thread.currentThread().interrupt();
|
|
248
|
+
} finally {
|
|
249
|
+
done.countDown();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
Thread t2 =
|
|
253
|
+
new Thread(
|
|
254
|
+
() -> {
|
|
255
|
+
try {
|
|
256
|
+
start.await();
|
|
257
|
+
prevB.set(map.putReplacing(1, third));
|
|
258
|
+
} catch (InterruptedException e) {
|
|
259
|
+
Thread.currentThread().interrupt();
|
|
260
|
+
} finally {
|
|
261
|
+
done.countDown();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
t1.start();
|
|
266
|
+
t2.start();
|
|
267
|
+
start.countDown();
|
|
268
|
+
assertTrue(done.await(5, TimeUnit.SECONDS));
|
|
269
|
+
|
|
270
|
+
Object winner = map.get(1);
|
|
271
|
+
assertTrue(winner == second || winner == third);
|
|
272
|
+
Object returnedFirst = prevA.get() == first ? prevA.get() : prevB.get();
|
|
273
|
+
assertSame(first, returnedFirst);
|
|
274
|
+
}
|
|
275
|
+
}
|