@react-native-firebase/auth 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 +9 -0
- package/RNFBAuth.podspec +1 -1
- package/android/build.gradle +2 -0
- package/android/src/main/java/io/invertase/firebase/auth/AuthListenerHandle.java +26 -0
- package/android/src/main/java/io/invertase/firebase/auth/NativeRNFBTurboAuth.java +69 -101
- package/android/src/main/java/io/invertase/firebase/auth/RNFBAuthCacheRegistry.java +66 -0
- package/android/src/main/java/io/invertase/firebase/auth/RNFBAuthListenerRegistry.java +76 -0
- package/android/src/test/java/io/invertase/firebase/auth/RNFBAuthCacheRegistryTest.java +172 -0
- package/android/src/test/java/io/invertase/firebase/auth/RNFBAuthListenerRegistryTest.java +171 -0
- package/dist/module/ActionCodeURL.js +1 -1
- package/dist/module/ActionCodeURL.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/ActionCodeURL.d.ts.map +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/ios/RNFBAuth/RNFBAuthCacheRegistry.h +41 -0
- package/ios/RNFBAuth/RNFBAuthCacheRegistry.m +65 -0
- package/ios/RNFBAuth/RNFBAuthHelper.m +124 -109
- package/ios/RNFBAuth/RNFBAuthListenerRegistry.h +39 -0
- package/ios/RNFBAuth/RNFBAuthListenerRegistry.m +83 -0
- package/ios/RNFBAuthUnitTests/RNFBAuthCacheRegistryTests.m +131 -0
- package/ios/RNFBAuthUnitTests/RNFBAuthListenerRegistryTests.m +152 -0
- package/ios/RNFBAuthUnitTests/RNFBAuthUnitTests.xcodeproj/project.pbxproj +261 -0
- package/ios/RNFBAuthUnitTests/RNFBAuthUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAuthUnitTests.xcscheme +69 -0
- package/lib/ActionCodeURL.ts +1 -2
- package/lib/version.ts +1 -1
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
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
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **auth, ios:** look up customAuthDomains with JS app name ([9873f61](https://github.com/invertase/react-native-firebase/commit/9873f618a972c3324ec05b9e30f5aaefa7ab163b))
|
|
11
|
+
- **auth:** [LOW] handle malformed action links ([0961ccc](https://github.com/invertase/react-native-firebase/commit/0961ccc1108195c40fbb00ba38280f70f1419fbe))
|
|
12
|
+
- **auth:** use RNFBHandleMap for auth state and id-token listeners ([a708e84](https://github.com/invertase/react-native-firebase/commit/a708e847119995b8cfbde07f11ea84d48aede29b))
|
|
13
|
+
- **auth:** use RNFBHandleMap for credential and MFA caches ([bd891f5](https://github.com/invertase/react-native-firebase/commit/bd891f5f8955c8c08d9d9447f2442beda91c9174))
|
|
14
|
+
|
|
6
15
|
## [26.3.3](https://github.com/invertase/react-native-firebase/compare/v26.3.2...v26.3.3) (2026-09-01)
|
|
7
16
|
|
|
8
17
|
### Bug Fixes
|
package/RNFBAuth.podspec
CHANGED
|
@@ -33,7 +33,7 @@ Pod::Spec.new do |s|
|
|
|
33
33
|
'ios/RNFBAuth/RNFBAuthModule.h',
|
|
34
34
|
'ios/generated/**/*.h',
|
|
35
35
|
]
|
|
36
|
-
s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*'
|
|
36
|
+
s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*', 'ios/*UnitTests/**'
|
|
37
37
|
|
|
38
38
|
s.pod_target_xcconfig = {
|
|
39
39
|
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ios/generated/RNFBAuthTurboModules\" \"$(PODS_TARGET_SRCROOT)/ios/generated\"",
|
package/android/build.gradle
CHANGED
|
@@ -91,6 +91,8 @@ dependencies {
|
|
|
91
91
|
api appProject
|
|
92
92
|
implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion("firebase", "bom")}")
|
|
93
93
|
implementation "com.google.firebase:firebase-auth"
|
|
94
|
+
|
|
95
|
+
testImplementation "junit:junit:4.13.2"
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
ReactNative.shared.applyPackageVersion()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package io.invertase.firebase.auth;
|
|
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
|
+
* Detach surface stored in {@link RNFBAuthListenerRegistry}. Invoked after {@code get}/{@code
|
|
22
|
+
* take}/{@code takeAll}, never under the HandleMap lock.
|
|
23
|
+
*/
|
|
24
|
+
interface AuthListenerHandle {
|
|
25
|
+
void remove();
|
|
26
|
+
}
|
|
@@ -84,7 +84,6 @@ import java.text.SimpleDateFormat;
|
|
|
84
84
|
import java.util.ArrayList;
|
|
85
85
|
import java.util.Date;
|
|
86
86
|
import java.util.HashMap;
|
|
87
|
-
import java.util.Iterator;
|
|
88
87
|
import java.util.List;
|
|
89
88
|
import java.util.Locale;
|
|
90
89
|
import java.util.Map;
|
|
@@ -104,21 +103,23 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
104
103
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
|
105
104
|
|
|
106
105
|
private static final String TAG = "Auth";
|
|
107
|
-
private static
|
|
108
|
-
private static
|
|
106
|
+
private static final RNFBAuthListenerRegistry authStateListeners = new RNFBAuthListenerRegistry();
|
|
107
|
+
private static final RNFBAuthListenerRegistry idTokenListeners = new RNFBAuthListenerRegistry();
|
|
109
108
|
private static HashMap<String, String> emulatorConfigs = new HashMap<>();
|
|
110
109
|
private String mVerificationId;
|
|
111
110
|
private String mLastPhoneNumber;
|
|
112
111
|
private PhoneAuthProvider.ForceResendingToken mForceResendingToken;
|
|
113
112
|
private PhoneAuthCredential mCredential;
|
|
114
113
|
|
|
115
|
-
private final
|
|
116
|
-
|
|
117
|
-
private final
|
|
114
|
+
private final RNFBAuthCacheRegistry<MultiFactorResolver> mCachedResolvers =
|
|
115
|
+
new RNFBAuthCacheRegistry<>();
|
|
116
|
+
private final RNFBAuthCacheRegistry<MultiFactorSession> mMultiFactorSessions =
|
|
117
|
+
new RNFBAuthCacheRegistry<>();
|
|
118
|
+
private final RNFBAuthCacheRegistry<TotpSecret> mTotpSecrets = new RNFBAuthCacheRegistry<>();
|
|
118
119
|
|
|
119
120
|
// storage for anonymous phone auth credentials, used for linkWithCredentials
|
|
120
121
|
// https://github.com/invertase/react-native-firebase/issues/4911
|
|
121
|
-
private
|
|
122
|
+
private final RNFBAuthCacheRegistry<AuthCredential> credentials = new RNFBAuthCacheRegistry<>();
|
|
122
123
|
|
|
123
124
|
private final TaskExecutorService executorService;
|
|
124
125
|
|
|
@@ -142,34 +143,13 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
142
143
|
super.invalidate();
|
|
143
144
|
Log.d(TAG, "instance-destroyed");
|
|
144
145
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
while (authListenerIterator.hasNext()) {
|
|
148
|
-
Map.Entry pair = (Map.Entry) authListenerIterator.next();
|
|
149
|
-
String appName = (String) pair.getKey();
|
|
150
|
-
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
|
151
|
-
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
|
152
|
-
FirebaseAuth.AuthStateListener mAuthListener =
|
|
153
|
-
(FirebaseAuth.AuthStateListener) pair.getValue();
|
|
154
|
-
firebaseAuth.removeAuthStateListener(mAuthListener);
|
|
155
|
-
authListenerIterator.remove();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
Iterator idTokenListenerIterator = mIdTokenListeners.entrySet().iterator();
|
|
159
|
-
|
|
160
|
-
while (idTokenListenerIterator.hasNext()) {
|
|
161
|
-
Map.Entry pair = (Map.Entry) idTokenListenerIterator.next();
|
|
162
|
-
String appName = (String) pair.getKey();
|
|
163
|
-
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
|
164
|
-
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
|
165
|
-
FirebaseAuth.IdTokenListener mAuthListener = (FirebaseAuth.IdTokenListener) pair.getValue();
|
|
166
|
-
firebaseAuth.removeIdTokenListener(mAuthListener);
|
|
167
|
-
idTokenListenerIterator.remove();
|
|
168
|
-
}
|
|
146
|
+
authStateListeners.takeAllAndRemove();
|
|
147
|
+
idTokenListeners.takeAllAndRemove();
|
|
169
148
|
|
|
170
149
|
mCachedResolvers.clear();
|
|
171
150
|
mMultiFactorSessions.clear();
|
|
172
151
|
mTotpSecrets.clear();
|
|
152
|
+
// credentials intentionally not cleared (prior HashMap behavior)
|
|
173
153
|
}
|
|
174
154
|
|
|
175
155
|
@Override
|
|
@@ -199,46 +179,39 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
199
179
|
|
|
200
180
|
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
|
201
181
|
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
firebaseAuth1 -> {
|
|
206
|
-
FirebaseUser user = firebaseAuth1.getCurrentUser();
|
|
207
|
-
WritableMap eventBody = Arguments.createMap();
|
|
208
|
-
ReactNativeFirebaseEventEmitter emitter =
|
|
209
|
-
ReactNativeFirebaseEventEmitter.getSharedInstance();
|
|
210
|
-
if (user != null) {
|
|
211
|
-
eventBody.putString("appName", appName); // for js side distribution
|
|
212
|
-
eventBody.putMap("user", firebaseUserToMap(user));
|
|
213
|
-
} else {
|
|
214
|
-
eventBody.putString("appName", appName); // for js side distribution
|
|
215
|
-
}
|
|
216
|
-
Log.d(TAG, "addAuthStateListener:eventBody " + eventBody.toString());
|
|
182
|
+
if (authStateListeners.get(appName) != null) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
217
185
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
186
|
+
FirebaseAuth.AuthStateListener newAuthListener =
|
|
187
|
+
firebaseAuth1 -> {
|
|
188
|
+
FirebaseUser user = firebaseAuth1.getCurrentUser();
|
|
189
|
+
WritableMap eventBody = Arguments.createMap();
|
|
190
|
+
ReactNativeFirebaseEventEmitter emitter =
|
|
191
|
+
ReactNativeFirebaseEventEmitter.getSharedInstance();
|
|
192
|
+
if (user != null) {
|
|
193
|
+
eventBody.putString("appName", appName); // for js side distribution
|
|
194
|
+
eventBody.putMap("user", firebaseUserToMap(user));
|
|
195
|
+
} else {
|
|
196
|
+
eventBody.putString("appName", appName); // for js side distribution
|
|
197
|
+
}
|
|
198
|
+
Log.d(TAG, "addAuthStateListener:eventBody " + eventBody.toString());
|
|
222
199
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
200
|
+
ReactNativeFirebaseEvent event =
|
|
201
|
+
new ReactNativeFirebaseEvent("auth_state_changed", eventBody, appName);
|
|
202
|
+
emitter.sendEvent(event);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
firebaseAuth.addAuthStateListener(newAuthListener);
|
|
206
|
+
authStateListeners.putOrDiscard(
|
|
207
|
+
appName, () -> firebaseAuth.removeAuthStateListener(newAuthListener));
|
|
226
208
|
}
|
|
227
209
|
|
|
228
210
|
/** Removes the current auth state listener */
|
|
229
211
|
@Override
|
|
230
212
|
public void removeAuthStateListener(String appName) {
|
|
231
213
|
Log.d(TAG, "removeAuthStateListener");
|
|
232
|
-
|
|
233
|
-
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
|
234
|
-
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
|
235
|
-
|
|
236
|
-
FirebaseAuth.AuthStateListener mAuthListener = mAuthListeners.get(appName);
|
|
237
|
-
|
|
238
|
-
if (mAuthListener != null) {
|
|
239
|
-
firebaseAuth.removeAuthStateListener(mAuthListener);
|
|
240
|
-
mAuthListeners.remove(appName);
|
|
241
|
-
}
|
|
214
|
+
authStateListeners.takeAndRemove(appName);
|
|
242
215
|
}
|
|
243
216
|
|
|
244
217
|
/** Add a new id token listener - if one doesn't exist already */
|
|
@@ -249,46 +222,40 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
249
222
|
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
|
250
223
|
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
|
251
224
|
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
FirebaseUser user = firebaseAuth1.getCurrentUser();
|
|
256
|
-
ReactNativeFirebaseEventEmitter emitter =
|
|
257
|
-
ReactNativeFirebaseEventEmitter.getSharedInstance();
|
|
258
|
-
WritableMap eventBody = Arguments.createMap();
|
|
259
|
-
if (user != null) {
|
|
260
|
-
eventBody.putBoolean("authenticated", true);
|
|
261
|
-
eventBody.putString("appName", appName);
|
|
262
|
-
eventBody.putMap("user", firebaseUserToMap(user));
|
|
263
|
-
} else {
|
|
264
|
-
eventBody.putString("appName", appName);
|
|
265
|
-
eventBody.putBoolean("authenticated", false);
|
|
266
|
-
}
|
|
225
|
+
if (idTokenListeners.get(appName) != null) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
267
228
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
229
|
+
FirebaseAuth.IdTokenListener newIdTokenListener =
|
|
230
|
+
firebaseAuth1 -> {
|
|
231
|
+
FirebaseUser user = firebaseAuth1.getCurrentUser();
|
|
232
|
+
ReactNativeFirebaseEventEmitter emitter =
|
|
233
|
+
ReactNativeFirebaseEventEmitter.getSharedInstance();
|
|
234
|
+
WritableMap eventBody = Arguments.createMap();
|
|
235
|
+
if (user != null) {
|
|
236
|
+
eventBody.putBoolean("authenticated", true);
|
|
237
|
+
eventBody.putString("appName", appName);
|
|
238
|
+
eventBody.putMap("user", firebaseUserToMap(user));
|
|
239
|
+
} else {
|
|
240
|
+
eventBody.putString("appName", appName);
|
|
241
|
+
eventBody.putBoolean("authenticated", false);
|
|
242
|
+
}
|
|
272
243
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
244
|
+
ReactNativeFirebaseEvent event =
|
|
245
|
+
new ReactNativeFirebaseEvent("auth_id_token_changed", eventBody, appName);
|
|
246
|
+
emitter.sendEvent(event);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
firebaseAuth.addIdTokenListener(newIdTokenListener);
|
|
250
|
+
idTokenListeners.putOrDiscard(
|
|
251
|
+
appName, () -> firebaseAuth.removeIdTokenListener(newIdTokenListener));
|
|
276
252
|
}
|
|
277
253
|
|
|
278
254
|
/** Removes the current id token listener */
|
|
279
255
|
@Override
|
|
280
256
|
public void removeIdTokenListener(String appName) {
|
|
281
257
|
Log.d(TAG, "removeIdTokenListener");
|
|
282
|
-
|
|
283
|
-
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
|
284
|
-
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
|
285
|
-
|
|
286
|
-
FirebaseAuth.IdTokenListener mIdTokenListener = mIdTokenListeners.get(appName);
|
|
287
|
-
|
|
288
|
-
if (mIdTokenListener != null) {
|
|
289
|
-
firebaseAuth.removeIdTokenListener(mIdTokenListener);
|
|
290
|
-
mIdTokenListeners.remove(appName);
|
|
291
|
-
}
|
|
258
|
+
idTokenListeners.takeAndRemove(appName);
|
|
292
259
|
}
|
|
293
260
|
|
|
294
261
|
/**
|
|
@@ -1152,7 +1119,7 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
1152
1119
|
|
|
1153
1120
|
final MultiFactorSession session = task.getResult();
|
|
1154
1121
|
final String sessionId = Integer.toString(session.hashCode());
|
|
1155
|
-
mMultiFactorSessions.
|
|
1122
|
+
mMultiFactorSessions.putReplacing(sessionId, session);
|
|
1156
1123
|
|
|
1157
1124
|
promise.resolve(sessionId);
|
|
1158
1125
|
});
|
|
@@ -1493,7 +1460,7 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
1493
1460
|
if (task.isSuccessful()) {
|
|
1494
1461
|
TotpSecret totpSecret = task.getResult();
|
|
1495
1462
|
String totpSecretKey = totpSecret.getSharedSecretKey();
|
|
1496
|
-
mTotpSecrets.
|
|
1463
|
+
mTotpSecrets.putReplacing(totpSecretKey, totpSecret);
|
|
1497
1464
|
WritableMap result = Arguments.createMap();
|
|
1498
1465
|
result.putString("secretKey", totpSecretKey);
|
|
1499
1466
|
promise.resolve(result);
|
|
@@ -2061,8 +2028,9 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
2061
2028
|
if (provider.startsWith("oidc.")) {
|
|
2062
2029
|
return OAuthProvider.newCredentialBuilder(provider).setIdToken(authToken).build();
|
|
2063
2030
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2031
|
+
AuthCredential cachedCredential = credentials.get(authToken);
|
|
2032
|
+
if (cachedCredential != null) {
|
|
2033
|
+
return cachedCredential;
|
|
2066
2034
|
}
|
|
2067
2035
|
|
|
2068
2036
|
switch (provider) {
|
|
@@ -2463,7 +2431,7 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
2463
2431
|
authCredentialsMap.putString("secret", null);
|
|
2464
2432
|
|
|
2465
2433
|
// Temporarily store the non-serializable credential for later
|
|
2466
|
-
credentials.
|
|
2434
|
+
credentials.putReplacing(authHashCode, authCredential);
|
|
2467
2435
|
|
|
2468
2436
|
WritableMap userInfoMap = Arguments.createMap();
|
|
2469
2437
|
userInfoMap.putString("code", error.getString("code"));
|
|
@@ -2565,7 +2533,7 @@ public class NativeRNFBTurboAuth extends NativeRNFBTurboAuthSpec {
|
|
|
2565
2533
|
code = "MULTI_FACTOR_AUTH_REQUIRED";
|
|
2566
2534
|
final MultiFactorResolver resolver = multiFactorException.getResolver();
|
|
2567
2535
|
final String sessionId = Integer.toString(resolver.getSession().hashCode());
|
|
2568
|
-
mCachedResolvers.
|
|
2536
|
+
mCachedResolvers.putReplacing(sessionId, resolver);
|
|
2569
2537
|
// Passing around a resolver ReadableMap leads to issues when trying to send the data back by
|
|
2570
2538
|
// calling Promise#reject. Building the map just before sending solves that issue.
|
|
2571
2539
|
error.putString("sessionId", sessionId);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
package io.invertase.firebase.auth;
|
|
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 io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
21
|
+
import io.invertase.firebase.common.RNFBHandleMap;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Credential / MFA cache map (data only — no SDK cancel). Unique {@link #put}; callers that
|
|
25
|
+
* previously HashMap-upserted use {@link #putReplacing} (atomic last wins). Peek with {@link #get};
|
|
26
|
+
* remove with {@link #take}; invalidate with {@link #clear}.
|
|
27
|
+
*
|
|
28
|
+
* <p><strong>Collision vs prior HashMap.put upsert:</strong> unique {@link #put} throws; replace at
|
|
29
|
+
* call sites is {@link #putReplacing} (last wins). {@link #putOrDiscard} keeps the first mapping.
|
|
30
|
+
*/
|
|
31
|
+
final class RNFBAuthCacheRegistry<V> {
|
|
32
|
+
private final RNFBHandleMap<String, V> map = new RNFBHandleMap<>();
|
|
33
|
+
|
|
34
|
+
void put(String key, V value) throws RNFBHandleCollisionException {
|
|
35
|
+
map.put(key, value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Unique put. On collision, leave the existing mapping and discard the incoming value (data
|
|
40
|
+
* only).
|
|
41
|
+
*
|
|
42
|
+
* @return true if stored
|
|
43
|
+
*/
|
|
44
|
+
boolean putOrDiscard(String key, V value) {
|
|
45
|
+
return map.putIfAbsent(key, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Atomic replace (last wins). Preserves prior HashMap {@code put} upsert. Taken value discarded.
|
|
50
|
+
*/
|
|
51
|
+
void putReplacing(String key, V value) {
|
|
52
|
+
map.putReplacing(key, value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
V get(String key) {
|
|
56
|
+
return map.get(key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
V take(String key) {
|
|
60
|
+
return map.take(key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void clear() {
|
|
64
|
+
map.takeAll();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package io.invertase.firebase.auth;
|
|
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 io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
21
|
+
import io.invertase.firebase.common.RNFBHandleMap;
|
|
22
|
+
import java.util.List;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Auth-state / id-token listener map. Unique {@link #put} / {@link #putOrDiscard}; callers {@link
|
|
26
|
+
* #take} or {@link #takeAllAndRemove} then {@code remove()} outside the HandleMap lock.
|
|
27
|
+
* Skip-if-registered is {@code get(appName) != null}.
|
|
28
|
+
*/
|
|
29
|
+
final class RNFBAuthListenerRegistry {
|
|
30
|
+
private final RNFBHandleMap<String, AuthListenerHandle> map = new RNFBHandleMap<>();
|
|
31
|
+
|
|
32
|
+
void put(String appName, AuthListenerHandle handle) throws RNFBHandleCollisionException {
|
|
33
|
+
map.put(appName, handle);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Unique put. On collision, {@code remove()} the incoming handle and leave the existing mapping.
|
|
38
|
+
*
|
|
39
|
+
* @return true if stored
|
|
40
|
+
*/
|
|
41
|
+
boolean putOrDiscard(String appName, AuthListenerHandle handle) {
|
|
42
|
+
try {
|
|
43
|
+
map.put(appName, handle);
|
|
44
|
+
return true;
|
|
45
|
+
} catch (RNFBHandleCollisionException collision) {
|
|
46
|
+
if (handle != null) {
|
|
47
|
+
handle.remove();
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
AuthListenerHandle get(String appName) {
|
|
54
|
+
return map.get(appName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
AuthListenerHandle take(String appName) {
|
|
58
|
+
return map.take(appName);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void takeAndRemove(String appName) {
|
|
62
|
+
AuthListenerHandle handle = map.take(appName);
|
|
63
|
+
if (handle != null) {
|
|
64
|
+
handle.remove();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void takeAllAndRemove() {
|
|
69
|
+
List<AuthListenerHandle> remaining = map.takeAll();
|
|
70
|
+
for (AuthListenerHandle handle : remaining) {
|
|
71
|
+
if (handle != null) {
|
|
72
|
+
handle.remove();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
package io.invertase.firebase.auth;
|
|
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.assertFalse;
|
|
21
|
+
import static org.junit.Assert.assertNull;
|
|
22
|
+
import static org.junit.Assert.assertSame;
|
|
23
|
+
import static org.junit.Assert.assertTrue;
|
|
24
|
+
import static org.junit.Assert.fail;
|
|
25
|
+
|
|
26
|
+
import io.invertase.firebase.common.RNFBHandleCollisionException;
|
|
27
|
+
import java.util.concurrent.CountDownLatch;
|
|
28
|
+
import java.util.concurrent.TimeUnit;
|
|
29
|
+
import java.util.concurrent.atomic.AtomicReference;
|
|
30
|
+
import org.junit.Test;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* JVM coverage for {@link RNFBAuthCacheRegistry}. Does not instantiate {@code NativeRNFBTurboAuth}
|
|
34
|
+
* (React Native / Firebase) — D12.
|
|
35
|
+
*/
|
|
36
|
+
public class RNFBAuthCacheRegistryTest {
|
|
37
|
+
|
|
38
|
+
@Test
|
|
39
|
+
public void putGetTake_happyPath() throws Exception {
|
|
40
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
41
|
+
registry.put("k1", "payload");
|
|
42
|
+
assertSame("payload", registry.get("k1"));
|
|
43
|
+
assertSame("payload", registry.take("k1"));
|
|
44
|
+
assertNull(registry.get("k1"));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Test
|
|
48
|
+
public void put_occupiedId_throwsCollision() throws Exception {
|
|
49
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
50
|
+
registry.put("k1", "first");
|
|
51
|
+
try {
|
|
52
|
+
registry.put("k1", "second");
|
|
53
|
+
fail("expected RNFBHandleCollisionException");
|
|
54
|
+
} catch (RNFBHandleCollisionException e) {
|
|
55
|
+
assertTrue(e.getMessage().contains("k1"));
|
|
56
|
+
assertSame("first", registry.get("k1"));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Test
|
|
61
|
+
public void get_whenFree_isNull() {
|
|
62
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
63
|
+
assertNull(registry.get("missing"));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Test
|
|
67
|
+
public void take_whenFree_isNull() {
|
|
68
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
69
|
+
assertNull(registry.take("missing"));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
public void putOrDiscard_storesWhenFree() {
|
|
74
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
75
|
+
assertTrue(registry.putOrDiscard("k1", "payload"));
|
|
76
|
+
assertSame("payload", registry.get("k1"));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test
|
|
80
|
+
public void putOrDiscard_collision_keepsExisting() throws Exception {
|
|
81
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
82
|
+
registry.put("k1", "first");
|
|
83
|
+
assertFalse(registry.putOrDiscard("k1", "second"));
|
|
84
|
+
assertSame("first", registry.get("k1"));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Test
|
|
88
|
+
public void putReplacing_whenFree_stores() {
|
|
89
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
90
|
+
registry.putReplacing("k1", "payload");
|
|
91
|
+
assertSame("payload", registry.get("k1"));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Test
|
|
95
|
+
public void putReplacing_whenOccupied_replacesLastWins() throws Exception {
|
|
96
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
97
|
+
registry.put("k1", "first");
|
|
98
|
+
registry.putReplacing("k1", "second");
|
|
99
|
+
assertSame("second", registry.get("k1"));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Test
|
|
103
|
+
public void putReplacing_sameIdFromTwoThreads_lastWins() throws Exception {
|
|
104
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
105
|
+
registry.put("k1", "first");
|
|
106
|
+
|
|
107
|
+
CountDownLatch start = new CountDownLatch(1);
|
|
108
|
+
CountDownLatch done = new CountDownLatch(2);
|
|
109
|
+
AtomicReference<String> winner = new AtomicReference<>();
|
|
110
|
+
|
|
111
|
+
Thread t1 =
|
|
112
|
+
new Thread(
|
|
113
|
+
() -> {
|
|
114
|
+
try {
|
|
115
|
+
start.await();
|
|
116
|
+
registry.putReplacing("k1", "second");
|
|
117
|
+
winner.compareAndSet(null, registry.get("k1"));
|
|
118
|
+
} catch (InterruptedException e) {
|
|
119
|
+
Thread.currentThread().interrupt();
|
|
120
|
+
} finally {
|
|
121
|
+
done.countDown();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
Thread t2 =
|
|
125
|
+
new Thread(
|
|
126
|
+
() -> {
|
|
127
|
+
try {
|
|
128
|
+
start.await();
|
|
129
|
+
registry.putReplacing("k1", "third");
|
|
130
|
+
winner.compareAndSet(null, registry.get("k1"));
|
|
131
|
+
} catch (InterruptedException e) {
|
|
132
|
+
Thread.currentThread().interrupt();
|
|
133
|
+
} finally {
|
|
134
|
+
done.countDown();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
t1.start();
|
|
139
|
+
t2.start();
|
|
140
|
+
start.countDown();
|
|
141
|
+
assertTrue(done.await(5, TimeUnit.SECONDS));
|
|
142
|
+
|
|
143
|
+
String stored = registry.get("k1");
|
|
144
|
+
assertTrue(stored == "second" || stored == "third");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@Test
|
|
148
|
+
public void put_afterTake_allowsReuse() throws Exception {
|
|
149
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
150
|
+
registry.put("k1", "first");
|
|
151
|
+
assertSame("first", registry.take("k1"));
|
|
152
|
+
registry.put("k1", "second");
|
|
153
|
+
assertSame("second", registry.get("k1"));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@Test
|
|
157
|
+
public void clear_removesAll() throws Exception {
|
|
158
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
159
|
+
registry.put("a", "1");
|
|
160
|
+
registry.put("b", "2");
|
|
161
|
+
registry.clear();
|
|
162
|
+
assertNull(registry.get("a"));
|
|
163
|
+
assertNull(registry.get("b"));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@Test
|
|
167
|
+
public void clear_empty_isNoOp() {
|
|
168
|
+
RNFBAuthCacheRegistry<String> registry = new RNFBAuthCacheRegistry<>();
|
|
169
|
+
registry.clear();
|
|
170
|
+
assertNull(registry.get("k1"));
|
|
171
|
+
}
|
|
172
|
+
}
|