@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.
Files changed (26) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/RNFBAuth.podspec +1 -1
  3. package/android/build.gradle +2 -0
  4. package/android/src/main/java/io/invertase/firebase/auth/AuthListenerHandle.java +26 -0
  5. package/android/src/main/java/io/invertase/firebase/auth/NativeRNFBTurboAuth.java +69 -101
  6. package/android/src/main/java/io/invertase/firebase/auth/RNFBAuthCacheRegistry.java +66 -0
  7. package/android/src/main/java/io/invertase/firebase/auth/RNFBAuthListenerRegistry.java +76 -0
  8. package/android/src/test/java/io/invertase/firebase/auth/RNFBAuthCacheRegistryTest.java +172 -0
  9. package/android/src/test/java/io/invertase/firebase/auth/RNFBAuthListenerRegistryTest.java +171 -0
  10. package/dist/module/ActionCodeURL.js +1 -1
  11. package/dist/module/ActionCodeURL.js.map +1 -1
  12. package/dist/module/version.js +1 -1
  13. package/dist/typescript/lib/ActionCodeURL.d.ts.map +1 -1
  14. package/dist/typescript/lib/version.d.ts +1 -1
  15. package/ios/RNFBAuth/RNFBAuthCacheRegistry.h +41 -0
  16. package/ios/RNFBAuth/RNFBAuthCacheRegistry.m +65 -0
  17. package/ios/RNFBAuth/RNFBAuthHelper.m +124 -109
  18. package/ios/RNFBAuth/RNFBAuthListenerRegistry.h +39 -0
  19. package/ios/RNFBAuth/RNFBAuthListenerRegistry.m +83 -0
  20. package/ios/RNFBAuthUnitTests/RNFBAuthCacheRegistryTests.m +131 -0
  21. package/ios/RNFBAuthUnitTests/RNFBAuthListenerRegistryTests.m +152 -0
  22. package/ios/RNFBAuthUnitTests/RNFBAuthUnitTests.xcodeproj/project.pbxproj +261 -0
  23. package/ios/RNFBAuthUnitTests/RNFBAuthUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAuthUnitTests.xcscheme +69 -0
  24. package/lib/ActionCodeURL.ts +1 -2
  25. package/lib/version.ts +1 -1
  26. package/package.json +5 -4
@@ -36,7 +36,24 @@
36
36
  #import "RNFBApp/RCTConvert+FIRApp.h"
37
37
  #import "RNFBApp/RNFBAppModule.h"
38
38
  #import "RNFBApp/RNFBSharedUtils.h"
39
+ #import "RNFBAuthCacheRegistry.h"
39
40
  #import "RNFBAuthHelper.h"
41
+ #import "RNFBAuthListenerRegistry.h"
42
+
43
+ @interface RNFBAuthListenerRemover : NSObject
44
+ @property(nonatomic, copy, nullable) void (^onRemove)(void);
45
+ - (void)remove;
46
+ @end
47
+
48
+ @implementation RNFBAuthListenerRemover
49
+ - (void)remove {
50
+ if (self.onRemove != nil) {
51
+ void (^block)(void) = self.onRemove;
52
+ self.onRemove = nil;
53
+ block();
54
+ }
55
+ }
56
+ @end
40
57
 
41
58
  static void RNFBAuthThrowSyncErrorWithCode(NSString *code, NSString *message) {
42
59
  @throw [NSException exceptionWithName:code
@@ -133,15 +150,15 @@ static NSString *const AuthErrorCode_toJSErrorCode[] = {
133
150
  [FIRAuthErrorCodeMalformedJWT] = @"malformed-jwt",
134
151
  [FIRAuthErrorCodeSecondFactorRequired] = @"multi-factor-auth-required"};
135
152
 
136
- static __strong NSMutableDictionary *authStateHandlers;
137
- static __strong NSMutableDictionary *idTokenHandlers;
153
+ static __strong RNFBAuthListenerRegistry *authStateHandlers;
154
+ static __strong RNFBAuthListenerRegistry *idTokenHandlers;
138
155
  static __strong NSMutableDictionary *emulatorConfigs;
139
156
  // Used for caching credentials between method calls.
140
- static __strong NSMutableDictionary<NSString *, FIRAuthCredential *> *credentials;
157
+ static __strong RNFBAuthCacheRegistry *credentials;
141
158
  #if TARGET_OS_IOS
142
- static __strong NSMutableDictionary<NSString *, FIRMultiFactorResolver *> *cachedResolver;
143
- static __strong NSMutableDictionary<NSString *, FIRMultiFactorSession *> *cachedSessions;
144
- static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecrets;
159
+ static __strong RNFBAuthCacheRegistry *cachedResolver;
160
+ static __strong RNFBAuthCacheRegistry *cachedSessions;
161
+ static __strong RNFBAuthCacheRegistry *cachedTotpSecrets;
145
162
  #endif
146
163
 
147
164
  @interface RNFBAuthHelper ()
@@ -200,14 +217,14 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
200
217
  + (void)initializeSharedStateOnce {
201
218
  static dispatch_once_t onceToken;
202
219
  dispatch_once(&onceToken, ^{
203
- authStateHandlers = [[NSMutableDictionary alloc] init];
204
- idTokenHandlers = [[NSMutableDictionary alloc] init];
220
+ authStateHandlers = [[RNFBAuthListenerRegistry alloc] init];
221
+ idTokenHandlers = [[RNFBAuthListenerRegistry alloc] init];
205
222
  emulatorConfigs = [[NSMutableDictionary alloc] init];
206
- credentials = [[NSMutableDictionary alloc] init];
223
+ credentials = [[RNFBAuthCacheRegistry alloc] init];
207
224
  #if TARGET_OS_IOS
208
- cachedResolver = [[NSMutableDictionary alloc] init];
209
- cachedSessions = [[NSMutableDictionary alloc] init];
210
- cachedTotpSecrets = [[NSMutableDictionary alloc] init];
225
+ cachedResolver = [[RNFBAuthCacheRegistry alloc] init];
226
+ cachedSessions = [[RNFBAuthCacheRegistry alloc] init];
227
+ cachedTotpSecrets = [[RNFBAuthCacheRegistry alloc] init];
211
228
  #endif
212
229
  });
213
230
  }
@@ -215,26 +232,14 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
215
232
  + (void)invalidate {
216
233
  [self initializeSharedStateOnce];
217
234
 
218
- for (NSString *key in authStateHandlers) {
219
- FIRApp *firebaseApp = [RCTConvert firAppFromString:key];
220
-
221
- [[FIRAuth authWithApp:firebaseApp]
222
- removeAuthStateDidChangeListener:[authStateHandlers valueForKey:key]];
223
- }
224
- [authStateHandlers removeAllObjects];
225
-
226
- for (NSString *key in idTokenHandlers) {
227
- FIRApp *firebaseApp = [RCTConvert firAppFromString:key];
228
- [[FIRAuth authWithApp:firebaseApp]
229
- removeIDTokenDidChangeListener:[idTokenHandlers valueForKey:key]];
230
- }
231
- [idTokenHandlers removeAllObjects];
235
+ [authStateHandlers removeAll];
236
+ [idTokenHandlers removeAll];
232
237
 
233
- [credentials removeAllObjects];
238
+ [credentials clear];
234
239
  #if TARGET_OS_IOS
235
- [cachedResolver removeAllObjects];
236
- [cachedSessions removeAllObjects];
237
- [cachedTotpSecrets removeAllObjects];
240
+ [cachedResolver clear];
241
+ [cachedSessions clear];
242
+ [cachedTotpSecrets clear];
238
243
  #endif
239
244
  }
240
245
 
@@ -245,68 +250,75 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
245
250
  [self initializeSharedStateOnce];
246
251
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];
247
252
 
248
- if (![authStateHandlers valueForKey:firebaseApp.name]) {
249
- FIRAuthStateDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firebaseApp]
250
- addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
251
- if (user != nil) {
252
- [RNFBSharedUtils sendJSEventForApp:firebaseApp
253
- name:AUTH_STATE_CHANGED_EVENT
254
- body:@{keyUser : [self firebaseUserToDict:user]}];
255
- } else {
256
- [RNFBSharedUtils sendJSEventForApp:firebaseApp name:AUTH_STATE_CHANGED_EVENT body:@{}];
257
- }
258
- }];
259
- authStateHandlers[firebaseApp.name] = newListenerHandle;
253
+ if ([authStateHandlers get:firebaseApp.name] != nil) {
254
+ return;
260
255
  }
256
+
257
+ FIRAuth *auth = [FIRAuth authWithApp:firebaseApp];
258
+ FIRAuthStateDidChangeListenerHandle newListenerHandle = [auth
259
+ addAuthStateDidChangeListener:^(FIRAuth *_Nonnull authInstance, FIRUser *_Nullable user) {
260
+ if (user != nil) {
261
+ [RNFBSharedUtils sendJSEventForApp:firebaseApp
262
+ name:AUTH_STATE_CHANGED_EVENT
263
+ body:@{keyUser : [self firebaseUserToDict:user]}];
264
+ } else {
265
+ [RNFBSharedUtils sendJSEventForApp:firebaseApp name:AUTH_STATE_CHANGED_EVENT body:@{}];
266
+ }
267
+ }];
268
+
269
+ RNFBAuthListenerRemover *remover = [[RNFBAuthListenerRemover alloc] init];
270
+ remover.onRemove = ^{
271
+ [auth removeAuthStateDidChangeListener:newListenerHandle];
272
+ };
273
+ [authStateHandlers putOrDiscard:firebaseApp.name value:remover];
261
274
  }
262
275
 
263
276
  + (void)removeAuthStateListener:(NSString *)appName {
264
277
  [self initializeSharedStateOnce];
265
278
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];
266
-
267
- if ([authStateHandlers valueForKey:firebaseApp.name]) {
268
- [[FIRAuth authWithApp:firebaseApp]
269
- removeAuthStateDidChangeListener:[authStateHandlers valueForKey:firebaseApp.name]];
270
- [authStateHandlers removeObjectForKey:firebaseApp.name];
271
- }
279
+ [authStateHandlers takeAndRemove:firebaseApp.name];
272
280
  }
273
281
 
274
282
  + (void)addIdTokenListener:(NSString *)appName {
275
283
  [self initializeSharedStateOnce];
276
284
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];
277
285
 
278
- if (![idTokenHandlers valueForKey:firebaseApp.name]) {
279
- FIRIDTokenDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firebaseApp]
280
- addIDTokenDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
281
- if (user != nil) {
282
- [RNFBSharedUtils sendJSEventForApp:firebaseApp
283
- name:AUTH_ID_TOKEN_CHANGED_EVENT
284
- body:@{keyUser : [self firebaseUserToDict:user]}];
285
- } else {
286
- [RNFBSharedUtils sendJSEventForApp:firebaseApp
287
- name:AUTH_ID_TOKEN_CHANGED_EVENT
288
- body:@{}];
289
- }
290
- }];
291
- idTokenHandlers[firebaseApp.name] = newListenerHandle;
286
+ if ([idTokenHandlers get:firebaseApp.name] != nil) {
287
+ return;
292
288
  }
289
+
290
+ FIRAuth *auth = [FIRAuth authWithApp:firebaseApp];
291
+ FIRIDTokenDidChangeListenerHandle newListenerHandle =
292
+ [auth addIDTokenDidChangeListener:^(FIRAuth *_Nonnull authInstance, FIRUser *_Nullable user) {
293
+ if (user != nil) {
294
+ [RNFBSharedUtils sendJSEventForApp:firebaseApp
295
+ name:AUTH_ID_TOKEN_CHANGED_EVENT
296
+ body:@{keyUser : [self firebaseUserToDict:user]}];
297
+ } else {
298
+ [RNFBSharedUtils sendJSEventForApp:firebaseApp name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{}];
299
+ }
300
+ }];
301
+
302
+ RNFBAuthListenerRemover *remover = [[RNFBAuthListenerRemover alloc] init];
303
+ remover.onRemove = ^{
304
+ [auth removeIDTokenDidChangeListener:newListenerHandle];
305
+ };
306
+ [idTokenHandlers putOrDiscard:firebaseApp.name value:remover];
293
307
  }
294
308
 
295
309
  + (void)removeIdTokenListener:(NSString *)appName {
296
310
  [self initializeSharedStateOnce];
297
311
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];
298
-
299
- if ([idTokenHandlers valueForKey:firebaseApp.name]) {
300
- [[FIRAuth authWithApp:firebaseApp]
301
- removeIDTokenDidChangeListener:[idTokenHandlers valueForKey:firebaseApp.name]];
302
- [idTokenHandlers removeObjectForKey:firebaseApp.name];
303
- }
312
+ [idTokenHandlers takeAndRemove:firebaseApp.name];
304
313
  }
305
314
 
306
315
  + (void)configureAuthDomain:(NSString *)appName {
307
316
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];
308
317
 
309
- NSString *authDomain = [RNFBAppModule getCustomDomain:firebaseApp.name];
318
+ // customAuthDomains is keyed by the JS app name ([DEFAULT]); FIRApp.name for the default app
319
+ // is __FIRAPP_DEFAULT. Look up with the existing JS-name converter (no parallel key helper).
320
+ NSString *authDomain =
321
+ [RNFBAppModule getCustomDomain:[RNFBSharedUtils getAppJavaScriptName:firebaseApp.name]];
310
322
  DLog(@"RNFBAuth app: %@ customAuthDomain: %@", firebaseApp.name, authDomain);
311
323
  if (authDomain != nil) {
312
324
  [FIRAuth authWithApp:firebaseApp].customAuthDomain = authDomain;
@@ -1054,7 +1066,8 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1054
1066
  [self initializeSharedStateOnce];
1055
1067
  FIRApp *firebaseApp = [RCTConvert firAppFromString:appName];
1056
1068
 
1057
- if ([cachedResolver valueForKey:sessionKey] == nil) {
1069
+ FIRMultiFactorResolver *resolver = [cachedResolver get:sessionKey];
1070
+ if (resolver == nil) {
1058
1071
  [RNFBSharedUtils
1059
1072
  rejectPromiseWithUserInfo:reject
1060
1073
  userInfo:(NSMutableDictionary *)@{
@@ -1063,10 +1076,10 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1063
1076
  }];
1064
1077
  return;
1065
1078
  }
1066
- FIRMultiFactorSession *session = cachedResolver[sessionKey].session;
1079
+ FIRMultiFactorSession *session = resolver.session;
1067
1080
  NSPredicate *findByUid = [NSPredicate predicateWithFormat:@"UID == %@", hintUid];
1068
1081
  FIRMultiFactorInfo *_Nullable hint =
1069
- [[cachedResolver[sessionKey].hints filteredArrayUsingPredicate:findByUid] firstObject];
1082
+ [[resolver.hints filteredArrayUsingPredicate:findByUid] firstObject];
1070
1083
  if (hint == nil) {
1071
1084
  [RNFBSharedUtils rejectPromiseWithUserInfo:reject
1072
1085
  userInfo:(NSMutableDictionary *)@{
@@ -1104,7 +1117,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1104
1117
  DLog(@"verifyPhoneNumberForMultifactor using app: %@", firebaseApp.name);
1105
1118
  DLog(@"verifyPhoneNumberForMultifactor phoneNumber: %@", phoneNumber);
1106
1119
  DLog(@"verifyPhoneNumberForMultifactor sessionKey: %@", sessionKey);
1107
- FIRMultiFactorSession *session = cachedSessions[sessionKey];
1120
+ FIRMultiFactorSession *session = [cachedSessions get:sessionKey];
1108
1121
  if (session == nil) {
1109
1122
  [RNFBSharedUtils rejectPromiseWithUserInfo:reject
1110
1123
  userInfo:(NSMutableDictionary *)@{
@@ -1147,18 +1160,19 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1147
1160
  FIRMultiFactorAssertion *assertion =
1148
1161
  [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
1149
1162
 
1150
- [cachedResolver[session] resolveSignInWithAssertion:assertion
1151
- completion:^(FIRAuthDataResult *_Nullable authResult,
1152
- NSError *_Nullable error) {
1153
- DLog(@"authError: %@", error) if (error) {
1154
- [self promiseRejectAuthException:reject error:error];
1155
- }
1156
- else {
1157
- [self promiseWithAuthResult:resolve
1158
- rejecter:reject
1159
- authResult:authResult];
1160
- }
1161
- }];
1163
+ FIRMultiFactorResolver *signInResolver = [cachedResolver get:session];
1164
+ [signInResolver resolveSignInWithAssertion:assertion
1165
+ completion:^(FIRAuthDataResult *_Nullable authResult,
1166
+ NSError *_Nullable error) {
1167
+ DLog(@"authError: %@", error) if (error) {
1168
+ [self promiseRejectAuthException:reject error:error];
1169
+ }
1170
+ else {
1171
+ [self promiseWithAuthResult:resolve
1172
+ rejecter:reject
1173
+ authResult:authResult];
1174
+ }
1175
+ }];
1162
1176
  }
1163
1177
 
1164
1178
  + (void)resolveTotpSignIn:(NSString *)appName
@@ -1175,19 +1189,19 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1175
1189
  FIRMultiFactorAssertion *assertion =
1176
1190
  [FIRTOTPMultiFactorGenerator assertionForSignInWithEnrollmentID:uid
1177
1191
  oneTimePassword:oneTimePassword];
1178
- [cachedResolver[sessionKey] resolveSignInWithAssertion:assertion
1179
- completion:^(FIRAuthDataResult *_Nullable authResult,
1180
- NSError *_Nullable error) {
1181
- DLog(@"authError: %@", error) if (error) {
1182
- [self promiseRejectAuthException:reject
1183
- error:error];
1184
- }
1185
- else {
1186
- [self promiseWithAuthResult:resolve
1187
- rejecter:reject
1188
- authResult:authResult];
1189
- }
1190
- }];
1192
+ FIRMultiFactorResolver *totpSignInResolver = [cachedResolver get:sessionKey];
1193
+ [totpSignInResolver resolveSignInWithAssertion:assertion
1194
+ completion:^(FIRAuthDataResult *_Nullable authResult,
1195
+ NSError *_Nullable error) {
1196
+ DLog(@"authError: %@", error) if (error) {
1197
+ [self promiseRejectAuthException:reject error:error];
1198
+ }
1199
+ else {
1200
+ [self promiseWithAuthResult:resolve
1201
+ rejecter:reject
1202
+ authResult:authResult];
1203
+ }
1204
+ }];
1191
1205
  }
1192
1206
 
1193
1207
  + (void)generateTotpSecret:(NSString *)appName
@@ -1199,7 +1213,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1199
1213
 
1200
1214
  DLog(@"using instance resolve generateTotpSecret: %@", firebaseApp.name);
1201
1215
 
1202
- FIRMultiFactorSession *session = cachedSessions[sessionKey];
1216
+ FIRMultiFactorSession *session = [cachedSessions get:sessionKey];
1203
1217
  DLog(@"using sessionKey: %@", sessionKey);
1204
1218
  DLog(@"using session: %@", session);
1205
1219
  [FIRTOTPMultiFactorGenerator
@@ -1212,8 +1226,8 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1212
1226
  else {
1213
1227
  NSString *secretKey = totpSecret.sharedSecretKey;
1214
1228
  DLog(@"secretKey generated: %@", secretKey);
1215
- cachedTotpSecrets[secretKey] = totpSecret;
1216
- DLog(@"cachedSecret: %@", cachedTotpSecrets[secretKey]);
1229
+ [cachedTotpSecrets putReplacing:secretKey value:totpSecret];
1230
+ DLog(@"cachedSecret: %@", [cachedTotpSecrets get:secretKey]);
1217
1231
  resolve(@{
1218
1232
  @"secretKey" : secretKey,
1219
1233
  });
@@ -1230,7 +1244,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1230
1244
 
1231
1245
  DLog(@"generateQrCodeUrl using instance resolve generateQrCodeUrl: %@", firebaseApp.name);
1232
1246
  DLog(@"generateQrCodeUrl using secretKey: %@", secretKey);
1233
- FIRTOTPSecret *totpSecret = cachedTotpSecrets[secretKey];
1247
+ FIRTOTPSecret *totpSecret = [cachedTotpSecrets get:secretKey];
1234
1248
  if (!totpSecret) {
1235
1249
  RNFBAuthThrowSyncErrorWithCode(@"invalid-multi-factor-secret",
1236
1250
  @"can't find secret for provided key");
@@ -1246,7 +1260,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1246
1260
  [self initializeSharedStateOnce];
1247
1261
 
1248
1262
  DLog(@"generateQrCodeUrl using secretKey: %@", secretKey);
1249
- FIRTOTPSecret *totpSecret = cachedTotpSecrets[secretKey];
1263
+ FIRTOTPSecret *totpSecret = [cachedTotpSecrets get:secretKey];
1250
1264
  DLog(@"openInOtpApp using qrCodeUri: %@", qrCodeUri);
1251
1265
  [totpSecret openInOTPAppWithQRCodeURL:qrCodeUri];
1252
1266
  }
@@ -1266,7 +1280,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1266
1280
  }
1267
1281
 
1268
1282
  NSString *sessionId = [NSString stringWithFormat:@"%@", @([session hash])];
1269
- cachedSessions[sessionId] = session;
1283
+ [cachedSessions putReplacing:sessionId value:session];
1270
1284
  resolve(sessionId);
1271
1285
  }];
1272
1286
  }
@@ -1333,7 +1347,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1333
1347
 
1334
1348
  DLog(@"using instance finalizeTotpEnrollment: %@", firebaseApp.name);
1335
1349
 
1336
- FIRTOTPSecret *cachedTotpSecret = cachedTotpSecrets[totpSecret];
1350
+ FIRTOTPSecret *cachedTotpSecret = [cachedTotpSecrets get:totpSecret];
1337
1351
  DLog(@"using totpSecretKey: %@", totpSecret);
1338
1352
  DLog(@"using cachedSecret: %@", cachedTotpSecret);
1339
1353
  FIRTOTPMultiFactorAssertion *assertion =
@@ -1789,8 +1803,9 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1789
1803
  FIRAuthCredential *credential;
1790
1804
 
1791
1805
  // First check if we cached an authToken
1792
- if (credentials[authToken] != nil && ![credentials[authToken] isEqual:[NSNull null]]) {
1793
- credential = credentials[authToken];
1806
+ FIRAuthCredential *cachedCredential = [credentials get:authToken];
1807
+ if (cachedCredential != nil) {
1808
+ credential = cachedCredential;
1794
1809
  } else if ([provider compare:@"twitter.com" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
1795
1810
  credential = [FIRTwitterAuthProvider credentialWithToken:authToken secret:authTokenSecret];
1796
1811
  } else if ([provider compare:@"facebook.com" options:NSCaseInsensitiveSearch] == NSOrderedSame &&
@@ -1986,7 +2001,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
1986
2001
  resolverDict = [self multiFactorResolverToDict:resolver];
1987
2002
 
1988
2003
  NSString *sessionKey = [NSString stringWithFormat:@"%@", @([resolver.session hash])];
1989
- cachedResolver[sessionKey] = resolver;
2004
+ [cachedResolver putReplacing:sessionKey value:resolver];
1990
2005
  }
1991
2006
  #endif
1992
2007
 
@@ -2175,7 +2190,7 @@ static __strong NSMutableDictionary<NSString *, FIRTOTPSecret *> *cachedTotpSecr
2175
2190
  NSString *authCredentialHash = [NSString stringWithFormat:@"%@", @([authCredential hash])];
2176
2191
 
2177
2192
  // Temporarily store the non-serializable credential for later
2178
- credentials[authCredentialHash] = authCredential;
2193
+ [credentials putReplacing:authCredentialHash value:authCredential];
2179
2194
 
2180
2195
  return @{
2181
2196
  keyProviderId : authCredential.provider,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #import <Foundation/Foundation.h>
19
+
20
+ NS_ASSUME_NONNULL_BEGIN
21
+
22
+ /**
23
+ * Auth-state / id-token listener map. Unique `put` / `putOrDiscard`; callers `take` or `removeAll`
24
+ * then `remove` outside the HandleMap lock. Skip-if-registered is `get != nil`. Stored values are
25
+ * expected to respond to `remove`.
26
+ */
27
+ @interface RNFBAuthListenerRegistry : NSObject
28
+
29
+ - (BOOL)put:(id)key value:(id)value error:(NSError *_Nullable *_Nullable)error;
30
+ /// Unique put. On collision, `remove`s the incoming value and leaves the existing mapping.
31
+ - (BOOL)putOrDiscard:(id)key value:(id)value;
32
+ - (nullable id)get:(id)key;
33
+ - (nullable id)take:(id)key;
34
+ - (void)takeAndRemove:(id)key;
35
+ - (void)removeAll;
36
+
37
+ @end
38
+
39
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #import "RNFBAuthListenerRegistry.h"
19
+
20
+ #if __has_include("RNFBHandleMap.h")
21
+ #import "RNFBHandleMap.h"
22
+ #else
23
+ #import "RNFBApp/RNFBHandleMap.h"
24
+ #endif
25
+
26
+ @protocol RNFBAuthRemovable <NSObject>
27
+ - (void)remove;
28
+ @end
29
+
30
+ @interface RNFBAuthListenerRegistry ()
31
+ @property(nonatomic, strong) RNFBHandleMap *map;
32
+ @end
33
+
34
+ @implementation RNFBAuthListenerRegistry
35
+
36
+ - (instancetype)init {
37
+ self = [super init];
38
+ if (self) {
39
+ _map = [[RNFBHandleMap alloc] init];
40
+ }
41
+ return self;
42
+ }
43
+
44
+ - (void)rnfb_removeHandle:(id)handle {
45
+ if (handle && [handle respondsToSelector:@selector(remove)]) {
46
+ [(id<RNFBAuthRemovable>)handle remove];
47
+ }
48
+ }
49
+
50
+ - (BOOL)put:(id)key value:(id)value error:(NSError **)error {
51
+ return [self.map put:key value:value error:error];
52
+ }
53
+
54
+ - (BOOL)putOrDiscard:(id)key value:(id)value {
55
+ NSError *error = nil;
56
+ if ([self.map put:key value:value error:&error]) {
57
+ return YES;
58
+ }
59
+ [self rnfb_removeHandle:value];
60
+ return NO;
61
+ }
62
+
63
+ - (id)get:(id)key {
64
+ return [self.map get:key];
65
+ }
66
+
67
+ - (id)take:(id)key {
68
+ return [self.map take:key];
69
+ }
70
+
71
+ - (void)takeAndRemove:(id)key {
72
+ id handle = [self.map take:key];
73
+ [self rnfb_removeHandle:handle];
74
+ }
75
+
76
+ - (void)removeAll {
77
+ NSArray *handlers = [self.map takeAll];
78
+ for (id handler in handlers) {
79
+ [self rnfb_removeHandle:handler];
80
+ }
81
+ }
82
+
83
+ @end
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #import <XCTest/XCTest.h>
19
+
20
+ #import "RNFBAuthCacheRegistry.h"
21
+ #import "RNFBHandleMap.h"
22
+
23
+ @interface RNFBAuthCacheRegistryTests : XCTestCase
24
+ @property(nonatomic, strong) RNFBAuthCacheRegistry *registry;
25
+ @end
26
+
27
+ @implementation RNFBAuthCacheRegistryTests
28
+
29
+ - (void)setUp {
30
+ [super setUp];
31
+ self.registry = [[RNFBAuthCacheRegistry alloc] init];
32
+ }
33
+
34
+ - (void)testPutGetTake_happyPath {
35
+ NSError *error = nil;
36
+ XCTAssertTrue([self.registry put:@"k1" value:@"payload" error:&error]);
37
+ XCTAssertNil(error);
38
+ XCTAssertEqualObjects(@"payload", [self.registry get:@"k1"]);
39
+ XCTAssertEqualObjects(@"payload", [self.registry take:@"k1"]);
40
+ XCTAssertNil([self.registry get:@"k1"]);
41
+ }
42
+
43
+ - (void)testPut_occupiedId_returnsCollision {
44
+ XCTAssertTrue([self.registry put:@"k1" value:@"first" error:nil]);
45
+
46
+ NSError *error = nil;
47
+ XCTAssertFalse([self.registry put:@"k1" value:@"second" error:&error]);
48
+ XCTAssertNotNil(error);
49
+ XCTAssertEqualObjects(error.domain, RNFBHandleMapErrorDomain);
50
+ XCTAssertEqual(error.code, RNFBHandleMapErrorCollision);
51
+ XCTAssertEqualObjects(@"first", [self.registry get:@"k1"]);
52
+ }
53
+
54
+ - (void)testPut_occupiedId_nilErrorOut_returnsNo {
55
+ XCTAssertTrue([self.registry put:@"k1" value:@"first" error:nil]);
56
+ XCTAssertFalse([self.registry put:@"k1" value:@"second" error:nil]);
57
+ XCTAssertEqualObjects(@"first", [self.registry get:@"k1"]);
58
+ }
59
+
60
+ - (void)testGet_whenFree_isNil {
61
+ XCTAssertNil([self.registry get:@"missing"]);
62
+ }
63
+
64
+ - (void)testTake_whenFree_isNil {
65
+ XCTAssertNil([self.registry take:@"missing"]);
66
+ }
67
+
68
+ - (void)testPutOrDiscard_storesWhenFree {
69
+ XCTAssertTrue([self.registry putOrDiscard:@"k1" value:@"payload"]);
70
+ XCTAssertEqualObjects(@"payload", [self.registry get:@"k1"]);
71
+ }
72
+
73
+ - (void)testPutOrDiscard_collision_keepsExisting {
74
+ XCTAssertTrue([self.registry put:@"k1" value:@"first" error:nil]);
75
+ XCTAssertFalse([self.registry putOrDiscard:@"k1" value:@"second"]);
76
+ XCTAssertEqualObjects(@"first", [self.registry get:@"k1"]);
77
+ }
78
+
79
+ - (void)testPutReplacing_whenFree_stores {
80
+ XCTAssertTrue([self.registry putReplacing:@"k1" value:@"payload"]);
81
+ XCTAssertEqualObjects(@"payload", [self.registry get:@"k1"]);
82
+ }
83
+
84
+ - (void)testPutReplacing_whenOccupied_replacesLastWins {
85
+ XCTAssertTrue([self.registry put:@"k1" value:@"first" error:nil]);
86
+ XCTAssertTrue([self.registry putReplacing:@"k1" value:@"second"]);
87
+ XCTAssertEqualObjects(@"second", [self.registry get:@"k1"]);
88
+ }
89
+
90
+ - (void)testPutReplacing_sameIdFromTwoThreads_lastWins {
91
+ XCTAssertTrue([self.registry put:@"k1" value:@"first" error:nil]);
92
+
93
+ dispatch_group_t group = dispatch_group_create();
94
+ dispatch_group_enter(group);
95
+ dispatch_group_enter(group);
96
+
97
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
98
+ [self.registry putReplacing:@"k1" value:@"second"];
99
+ dispatch_group_leave(group);
100
+ });
101
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
102
+ [self.registry putReplacing:@"k1" value:@"third"];
103
+ dispatch_group_leave(group);
104
+ });
105
+
106
+ dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
107
+ id stored = [self.registry get:@"k1"];
108
+ XCTAssertTrue([stored isEqual:@"second"] || [stored isEqual:@"third"]);
109
+ }
110
+
111
+ - (void)testPut_afterTake_allowsReuse {
112
+ XCTAssertTrue([self.registry put:@"k1" value:@"first" error:nil]);
113
+ XCTAssertEqualObjects(@"first", [self.registry take:@"k1"]);
114
+ XCTAssertTrue([self.registry put:@"k1" value:@"second" error:nil]);
115
+ XCTAssertEqualObjects(@"second", [self.registry get:@"k1"]);
116
+ }
117
+
118
+ - (void)testClear_removesAll {
119
+ XCTAssertTrue([self.registry put:@"a" value:@"1" error:nil]);
120
+ XCTAssertTrue([self.registry put:@"b" value:@"2" error:nil]);
121
+ [self.registry clear];
122
+ XCTAssertNil([self.registry get:@"a"]);
123
+ XCTAssertNil([self.registry get:@"b"]);
124
+ }
125
+
126
+ - (void)testClear_empty_isNoOp {
127
+ [self.registry clear];
128
+ XCTAssertNil([self.registry get:@"k1"]);
129
+ }
130
+
131
+ @end