Merge commit '36c936779b9badf72a0a3d789efafd2dcdb5ba36'

# Conflicts:
#	ios/RNFirebase.xcodeproj/project.pbxproj
#	package.json
This commit is contained in:
Chris Bianca 2017-10-31 14:33:31 +00:00
commit 5e12700c35
32 changed files with 692 additions and 272 deletions

View File

@ -33,6 +33,7 @@ import com.google.firebase.auth.ActionCodeResult;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
import com.google.firebase.auth.FirebaseAuthProvider;
import com.google.firebase.auth.GithubAuthProvider;
import com.google.firebase.auth.PhoneAuthCredential;
import com.google.firebase.auth.PhoneAuthProvider;
@ -54,6 +55,7 @@ import io.invertase.firebase.Utils;
class RNFirebaseAuth extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseAuth";
private String mVerificationId;
private PhoneAuthCredential mCredential;
private ReactContext mReactContext;
private HashMap<String, FirebaseAuth.AuthStateListener> mAuthListeners = new HashMap<>();
private HashMap<String, FirebaseAuth.IdTokenListener> mIdTokenListeners = new HashMap<>();
@ -738,10 +740,16 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
Log.d(TAG, "verifyPhoneNumber:" + phoneNumber);
// Reset the credential
mCredential = null;
PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) {
// Cache the credential to protect against null verificationId
mCredential = phoneAuthCredential;
Log.d(TAG, "verifyPhoneNumber:verification:onVerificationCompleted");
WritableMap state = Arguments.createMap();
@ -1068,6 +1076,15 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
case "github.com":
return GithubAuthProvider.getCredential(authToken);
case "phone":
// If the phone number is auto-verified quickly, then the verificationId can be null
// We cached the credential as part of the verifyPhoneNumber request to be re-used here
// if possible
if (authToken == null && mCredential != null) {
PhoneAuthCredential credential = mCredential;
// Reset the cached credential
mCredential = null;
return credential;
}
return PhoneAuthProvider.getCredential(authToken, authSecret);
case "password":
return EmailAuthProvider.getCredential(authToken, authSecret);
@ -1282,12 +1299,12 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
* @param providerData List<UserInfo> user.getProviderData()
* @return WritableArray array
*/
private WritableArray convertProviderData(List<? extends UserInfo> providerData) {
private WritableArray convertProviderData(List<? extends UserInfo> providerData, FirebaseUser user) {
WritableArray output = Arguments.createArray();
for (UserInfo userInfo : providerData) {
// remove 'firebase' provider data - android fb sdk
// should not be returning this as the ios/web ones don't
if (!userInfo.getProviderId().equals("firebase")) {
if (!FirebaseAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())) {
WritableMap userInfoMap = Arguments.createMap();
userInfoMap.putString("providerId", userInfo.getProviderId());
userInfoMap.putString("uid", userInfo.getUid());
@ -1295,20 +1312,34 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
final Uri photoUrl = userInfo.getPhotoUrl();
if (photoUrl != null) {
if (photoUrl != null && !"".equals(photoUrl)) {
userInfoMap.putString("photoURL", photoUrl.toString());
} else {
userInfoMap.putNull("photoURL");
}
final String phoneNumber = userInfo.getPhoneNumber();
if (phoneNumber != null) {
// The Android SDK is missing the phone number property for the phone provider when the
// user first signs up using their phone number. Use the phone number from the user
// object instead
if (PhoneAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())
&& (userInfo.getPhoneNumber() == null || "".equals(userInfo.getPhoneNumber()))) {
userInfoMap.putString("phoneNumber", user.getPhoneNumber());
} else if (phoneNumber != null && !"".equals(phoneNumber)) {
userInfoMap.putString("phoneNumber", phoneNumber);
} else {
userInfoMap.putNull("phoneNumber");
}
userInfoMap.putString("email", userInfo.getEmail());
// The Android SDK is missing the email property for the email provider, so we use UID instead
if (EmailAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())
&& (userInfo.getEmail() == null || "".equals(userInfo.getEmail()))) {
userInfoMap.putString("email", userInfo.getUid());
} else if (userInfo.getEmail() != null && !"".equals(userInfo.getEmail())) {
userInfoMap.putString("email", userInfo.getEmail());
} else {
userInfoMap.putNull("email");
}
output.pushMap(userInfoMap);
}
@ -1339,31 +1370,31 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
userMap.putBoolean("emailVerified", verified);
userMap.putBoolean("isAnonymous", user.isAnonymous());
if (email != null) {
if (email != null && !"".equals(email)) {
userMap.putString("email", email);
} else {
userMap.putNull("email");
}
if (name != null) {
if (name != null && !"".equals(name)) {
userMap.putString("displayName", name);
} else {
userMap.putNull("displayName");
}
if (photoUrl != null) {
if (photoUrl != null && !"".equals(photoUrl)) {
userMap.putString("photoURL", photoUrl.toString());
} else {
userMap.putNull("photoURL");
}
if (phoneNumber != null) {
if (phoneNumber != null && !"".equals(phoneNumber)) {
userMap.putString("phoneNumber", phoneNumber);
} else {
userMap.putNull("phoneNumber");
}
userMap.putArray("providerData", convertProviderData(user.getProviderData()));
userMap.putArray("providerData", convertProviderData(user.getProviderData(), user));
return userMap;
}

View File

@ -399,7 +399,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void on(String appName, ReadableMap props) {
getInternalReferenceForApp(appName, props)
getCachedInternalReferenceForApp(appName, props)
.on(
props.getString("eventType"),
props.getMap("registration")
@ -481,19 +481,13 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @return
*/
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, String key, String path, ReadableArray modifiers) {
RNFirebaseDatabaseReference existingRef = references.get(key);
if (existingRef == null) {
existingRef = new RNFirebaseDatabaseReference(
getReactApplicationContext(),
appName,
key,
path,
modifiers
);
}
return existingRef;
return new RNFirebaseDatabaseReference(
getReactApplicationContext(),
appName,
key,
path,
modifiers
);
}
/**
@ -503,7 +497,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
* @param props
* @return
*/
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, ReadableMap props) {
private RNFirebaseDatabaseReference getCachedInternalReferenceForApp(String appName, ReadableMap props) {
String key = props.getString("key");
String path = props.getString("path");
ReadableArray modifiers = props.getArray("modifiers");
@ -511,14 +505,7 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
RNFirebaseDatabaseReference existingRef = references.get(key);
if (existingRef == null) {
existingRef = new RNFirebaseDatabaseReference(
getReactApplicationContext(),
appName,
key,
path,
modifiers
);
existingRef = getInternalReferenceForApp(appName, key, path, modifiers);
references.put(key, existingRef);
}

View File

@ -31,8 +31,6 @@ import io.invertase.firebase.Utils;
public class FirestoreSerialize {
private static final String TAG = "FirestoreSerialize";
private static final DateFormat READ_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
private static final DateFormat WRITE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private static final String KEY_CHANGES = "changes";
private static final String KEY_DATA = "data";
private static final String KEY_DOC_CHANGE_DOCUMENT = "document";
@ -43,12 +41,6 @@ public class FirestoreSerialize {
private static final String KEY_METADATA = "metadata";
private static final String KEY_PATH = "path";
static {
// Javascript Date.toISOString is always formatted to UTC
// We set the read TimeZone to UTC to account for this
READ_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
/**
* Convert a DocumentSnapshot instance into a React Native WritableMap
*
@ -220,7 +212,7 @@ public class FirestoreSerialize {
typeMap.putMap("value", geoPoint);
} else if (value instanceof Date) {
typeMap.putString("type", "date");
typeMap.putString("value", WRITE_DATE_FORMAT.format((Date) value));
typeMap.putDouble("value", ((Date) value).getTime());
} else {
// TODO: Changed to log an error rather than crash - is this correct?
Log.e(TAG, "buildTypeMap: Cannot convert object of type " + value.getClass());
@ -275,13 +267,8 @@ public class FirestoreSerialize {
ReadableMap geoPoint = typeMap.getMap("value");
return new GeoPoint(geoPoint.getDouble("latitude"), geoPoint.getDouble("longitude"));
} else if ("date".equals(type)) {
try {
String date = typeMap.getString("value");
return READ_DATE_FORMAT.parse(date);
} catch (ParseException exception) {
Log.e(TAG, "parseTypeMap", exception);
return null;
}
Double time = typeMap.getDouble("value");
return new Date(time.longValue());
} else if ("fieldvalue".equals(type)) {
String value = typeMap.getString("value");
if ("delete".equals(value)) {

View File

@ -27,6 +27,7 @@
839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */; };
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.m */; };
839D91761EF3E20B0077C7C8 /* RNFirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D916B1EF3E20A0077C7C8 /* RNFirebaseStorage.m */; };
83C3EEEE1FA1EACC00B64D3C /* RNFirebaseUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3EEEC1FA1EACC00B64D3C /* RNFirebaseUtil.m */; };
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = D950369D1D19C77400F7094D /* RNFirebase.m */; };
/* End PBXBuildFile section */
@ -85,6 +86,8 @@
839D916A1EF3E20A0077C7C8 /* RNFirebaseStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseStorage.h; sourceTree = "<group>"; };
839D916B1EF3E20A0077C7C8 /* RNFirebaseStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseStorage.m; sourceTree = "<group>"; };
839D91771EF3E22F0077C7C8 /* RNFirebaseEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseEvents.h; path = RNFirebase/RNFirebaseEvents.h; sourceTree = "<group>"; };
83C3EEEC1FA1EACC00B64D3C /* RNFirebaseUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebaseUtil.m; path = RNFirebase/RNFirebaseUtil.m; sourceTree = "<group>"; };
83C3EEED1FA1EACC00B64D3C /* RNFirebaseUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebaseUtil.h; path = RNFirebase/RNFirebaseUtil.h; sourceTree = "<group>"; };
D950369C1D19C77400F7094D /* RNFirebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebase.h; path = RNFirebase/RNFirebase.h; sourceTree = "<group>"; };
D950369D1D19C77400F7094D /* RNFirebase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebase.m; path = RNFirebase/RNFirebase.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -131,11 +134,13 @@
8376F70D1F7C141500D45A85 /* firestore */,
839D91631EF3E20A0077C7C8 /* messaging */,
839D91661EF3E20A0077C7C8 /* perf */,
134814211AA4EA7D00B7C361 /* Products */,
134814211AA4EA7D00B7C361 /* storage */,
D950369C1D19C77400F7094D /* RNFirebase.h */,
D950369D1D19C77400F7094D /* RNFirebase.m */,
839D91771EF3E22F0077C7C8 /* RNFirebaseEvents.h */,
839D91691EF3E20A0077C7C8 /* storage */,
83C3EEED1FA1EACC00B64D3C /* RNFirebaseUtil.h */,
83C3EEEC1FA1EACC00B64D3C /* RNFirebaseUtil.m */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "<group>";
};
@ -327,6 +332,7 @@
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */,
839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */,
8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */,
83C3EEEE1FA1EACC00B64D3C /* RNFirebaseUtil.m in Sources */,
839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */,
839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */,
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */,

View File

@ -0,0 +1,14 @@
#ifndef RNFirebaseUtil_h
#define RNFirebaseUtil_h
#import <Foundation/Foundation.h>
#import <React/RCTEventEmitter.h>
@interface RNFirebaseUtil : NSObject
+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(NSDictionary *)body;
+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter appName:(NSString *)appName name:(NSString *)name body:(NSDictionary *)body;
@end
#endif

View File

@ -0,0 +1,25 @@
#import "RNFirebaseUtil.h"
@implementation RNFirebaseUtil
+ (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(NSDictionary *)body {
@try {
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (emitter.bridge) {
[emitter sendEventWithName:name body:body];
}
} @catch (NSException *error) {
NSLog(@"An error occurred in sendJSEvent: %@", [error debugDescription]);
}
}
+ (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter appName:(NSString *)appName name:(NSString *)name body:(NSDictionary *)body {
// Add the appName to the body
NSMutableDictionary *newBody = [body mutableCopy];
newBody[@"appName"] = appName;
[RNFirebaseUtil sendJSEvent:emitter name:name body:newBody];
}
@end

View File

@ -1,4 +1,5 @@
#import "RNFirebaseAdMobInterstitial.h"
#import "RNFirebaseUtil.h"
@implementation RNFirebaseAdMobInterstitial
@ -31,7 +32,7 @@
}
- (void)sendJSEvent:(NSString *)type payload:(NSDictionary *)payload {
[_delegate sendEventWithName:ADMOB_INTERSTITIAL_EVENT body:@{
[RNFirebaseUtil sendJSEvent:self.delegate name:ADMOB_INTERSTITIAL_EVENT body:@{
@"type": type,
@"adUnit": _adUnitID,
@"payload": payload

View File

@ -1,4 +1,5 @@
#import "RNFirebaseAdMobRewardedVideo.h"
#import "RNFirebaseUtil.h"
@implementation RNFirebaseAdMobRewardedVideo
@ -31,7 +32,7 @@
}
- (void)sendJSEvent:(NSString *)type payload:(NSDictionary *)payload {
[_delegate sendEventWithName:ADMOB_REWARDED_VIDEO_EVENT body:@{
[RNFirebaseUtil sendJSEvent:self.delegate name:ADMOB_REWARDED_VIDEO_EVENT body:@{
@"type": type,
@"adUnit": _adUnitID,
@"payload": payload
@ -73,4 +74,4 @@
#endif
@end
@end

View File

@ -1,5 +1,6 @@
#import "RNFirebaseAuth.h"
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import "RCTDefines.h"
@ -28,9 +29,9 @@ RCT_EXPORT_METHOD(addAuthStateListener:
FIRApp *firApp = [FIRApp appNamed:appName];
FIRAuthStateDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
if (user != nil) {
[self sendJSEventWithAppName:appName title:AUTH_CHANGED_EVENT props:[@{@"authenticated": @(true), @"user": [self firebaseUserToDict:user]} mutableCopy]];
[RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:AUTH_CHANGED_EVENT body:@{@"authenticated": @(true), @"user": [self firebaseUserToDict:user]}];
} else {
[self sendJSEventWithAppName:appName title:AUTH_CHANGED_EVENT props:[@{@"authenticated": @(false)} mutableCopy]];
[RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:AUTH_CHANGED_EVENT body:@{@"authenticated": @(false)}];
}
}];
@ -63,9 +64,9 @@ RCT_EXPORT_METHOD(addIdTokenListener:
FIRApp *firApp = [FIRApp appNamed:appName];
FIRIDTokenDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addIDTokenDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) {
if (user != nil) {
[self sendJSEventWithAppName:appName title:AUTH_ID_TOKEN_CHANGED_EVENT props:[@{@"authenticated": @(true), @"user": [self firebaseUserToDict:user]} mutableCopy]];
[RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{@"authenticated": @(true), @"user": [self firebaseUserToDict:user]}];
} else {
[self sendJSEventWithAppName:appName title:AUTH_ID_TOKEN_CHANGED_EVENT props:[@{@"authenticated": @(false)} mutableCopy]];
[RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{@"authenticated": @(false)}];
}
}];
@ -248,14 +249,7 @@ RCT_EXPORT_METHOD(reload:
FIRUser *user = [FIRAuth authWithApp:firApp].currentUser;
if (user) {
[user reloadWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
FIRUser *userAfterReload = [FIRAuth authWithApp:firApp].currentUser;
[self promiseWithUser:resolve rejecter:reject user:userAfterReload];
}
}];
[self reloadAndReturnUser:user resolver:resolve rejecter: reject];
} else {
[self promiseNoUser:resolve rejecter:reject isError:YES];
}
@ -315,8 +309,7 @@ RCT_EXPORT_METHOD(updateEmail:
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
FIRUser *userAfterUpdate = [FIRAuth authWithApp:firApp].currentUser;
[self promiseWithUser:resolve rejecter:reject user:userAfterUpdate];
[self reloadAndReturnUser:user resolver:resolve rejecter: reject];
}
}];
} else {
@ -399,8 +392,7 @@ RCT_EXPORT_METHOD(updateProfile:
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
FIRUser *userAfterUpdate = [FIRAuth authWithApp:firApp].currentUser;
[self promiseWithUser:resolve rejecter:reject user:userAfterUpdate];
[self reloadAndReturnUser:user resolver:resolve rejecter: reject];
}
}];
} else {
@ -686,21 +678,21 @@ RCT_EXPORT_METHOD(verifyPhoneNumber:(NSString *) appName
[[FIRPhoneAuthProvider providerWithAuth:[FIRAuth authWithApp:firApp]] verifyPhoneNumber:phoneNumber UIDelegate:nil completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
if (error) {
NSDictionary * jsError = [self getJSError:(error)];
NSMutableDictionary * props = [@{
@"type": @"onVerificationFailed",
@"requestKey":requestKey,
@"state": @{@"error": jsError},
} mutableCopy];
[self sendJSEventWithAppName:appName title:PHONE_AUTH_STATE_CHANGED_EVENT props: props];
NSDictionary *body = @{
@"type": @"onVerificationFailed",
@"requestKey":requestKey,
@"state": @{@"error": jsError},
};
[RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:PHONE_AUTH_STATE_CHANGED_EVENT body:body];
} else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:verificationID forKey:@"authVerificationID"];
NSMutableDictionary * props = [@{
@"type": @"onCodeSent",
@"requestKey":requestKey,
@"state": @{@"verificationId": verificationID},
} mutableCopy];
[self sendJSEventWithAppName:appName title:PHONE_AUTH_STATE_CHANGED_EVENT props: props];
NSDictionary *body = @{
@"type": @"onCodeSent",
@"requestKey":requestKey,
@"state": @{@"verificationId": verificationID},
};
[RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:PHONE_AUTH_STATE_CHANGED_EVENT body:body];
}
}];
}
@ -794,15 +786,7 @@ RCT_EXPORT_METHOD(unlink:
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
// This is here to protect against bugs in the iOS SDK which don't
// correctly refresh the user object when unlinking certain accounts
[user reloadWithCompletion:^(NSError * _Nullable error) {
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
[self promiseWithUser:resolve rejecter:reject user:user];
}
}];
[self reloadAndReturnUser:user resolver:resolve rejecter: reject];
}
}];
} else {
@ -916,6 +900,19 @@ RCT_EXPORT_METHOD(fetchProvidersForEmail:
return credential;
}
// This is here to protect against bugs in the iOS SDK which don't
// correctly refresh the user object when performing certain operations
- (void)reloadAndReturnUser:(FIRUser *)user
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
[user reloadWithCompletion:^(NSError * _Nullable error) {
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
[self promiseWithUser:resolve rejecter:reject user:user];
}
}];
}
/**
Resolve or reject a promise based on isError value
@ -1087,31 +1084,6 @@ RCT_EXPORT_METHOD(fetchProvidersForEmail:
}
/**
wrapper for sendEventWithName for auth events
@param title sendEventWithName
@param props <#props description#>
*/
- (void)sendJSEvent:(NSString *)title props:(NSDictionary *)props {
@try {
[self sendEventWithName:title body:props];
} @catch (NSException *error) {
NSLog(@"An error occurred in sendJSEvent: %@", [error debugDescription]);
}
}
- (void)sendJSEventWithAppName:(NSString *)appName title:(NSString *)title props:(NSMutableDictionary *)props {
props[@"appName"] = appName;
@try {
[self sendEventWithName:title body:props];
} @catch (NSException *error) {
NSLog(@"An error occurred in sendJSEvent: %@", [error debugDescription]);
}
}
/**
Converts an array of FIRUserInfo instances into the correct format to match the web sdk

View File

@ -5,6 +5,7 @@
#import <Firebase.h>
#import "RNFirebaseDatabaseReference.h"
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
@implementation RNFirebaseDatabase
RCT_EXPORT_MODULE();
@ -39,7 +40,7 @@ RCT_EXPORT_METHOD(keepSynced:(NSString *) appName
path:(NSString *) path
modifiers:(NSArray *) modifiers
state:(BOOL) state) {
FIRDatabaseQuery *query = [self getInternalReferenceForApp:appName key:key path:path modifiers:modifiers keep:false].query;
FIRDatabaseQuery *query = [self getInternalReferenceForApp:appName key:key path:path modifiers:modifiers].query;
[query keepSynced:state];
}
@ -87,11 +88,7 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
dispatch_barrier_async(_transactionQueue, ^{
[_transactions setValue:transactionState forKey:transactionId];
NSDictionary *updateMap = [self createTransactionUpdateMap:appName transactionId:transactionId updatesData:currentData];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (self.bridge) {
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:updateMap];
}
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:updateMap];
});
// wait for the js event handler to call tryCommitTransaction
@ -118,11 +115,7 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
andCompletionBlock:
^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
NSDictionary *resultMap = [self createTransactionResultMap:appName transactionId:transactionId error:databaseError committed:committed snapshot:snapshot];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (self.bridge) {
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:resultMap];
}
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:resultMap];
}
withLocalEvents:
applyLocally];
@ -233,13 +226,13 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
eventName:(NSString *) eventName
resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) {
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appName key:key path:path modifiers:modifiers keep:false];
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appName key:key path:path modifiers:modifiers];
[ref once:eventName resolver:resolve rejecter:reject];
}
RCT_EXPORT_METHOD(on:(NSString *) appName
props:(NSDictionary *) props) {
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appName key:props[@"key"] path:props[@"path"] modifiers:props[@"modifiers"] keep:false];
RNFirebaseDatabaseReference *ref = [self getCachedInternalReferenceForApp:appName props:props];
[ref on:props[@"eventType"] registration:props[@"registration"]];
}
@ -278,15 +271,20 @@ RCT_EXPORT_METHOD(off:(NSString *) key
return [[RNFirebaseDatabase getDatabaseForApp:appName] referenceWithPath:path];
}
- (RNFirebaseDatabaseReference *)getInternalReferenceForApp:(NSString *)appName key:(NSString *)key path:(NSString *)path modifiers:(NSArray *)modifiers keep:(BOOL)keep {
- (RNFirebaseDatabaseReference *)getInternalReferenceForApp:(NSString *)appName key:(NSString *)key path:(NSString *)path modifiers:(NSArray *)modifiers {
return [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self app:appName key:key refPath:path modifiers:modifiers];
}
- (RNFirebaseDatabaseReference *)getCachedInternalReferenceForApp:(NSString *)appName props:(NSDictionary *)props {
NSString *key = props[@"key"];
NSString *path = props[@"path"];
NSDictionary *modifiers = props[@"modifiers"];
RNFirebaseDatabaseReference *ref = _dbReferences[key];
if (ref == nil) {
ref = [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self app:appName key:key refPath:path modifiers:modifiers];
if (keep) {
_dbReferences[key] = ref;
}
_dbReferences[key] = ref;
}
return ref;
}

View File

@ -6,6 +6,7 @@
#import <FirebaseDatabase/FIRDatabase.h>
#import "RNFirebaseDatabase.h"
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <React/RCTEventEmitter.h>
@interface RNFirebaseDatabaseReference : NSObject

View File

@ -71,11 +71,7 @@
[event setValue:eventType forKey:@"eventType"];
[event setValue:registration forKey:@"registration"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:DATABASE_SYNC_EVENT body:event];
}
[RNFirebaseUtil sendJSEvent:self.emitter name:DATABASE_SYNC_EVENT body:event];
}
- (void)handleDatabaseError:(NSDictionary *) registration
@ -85,11 +81,7 @@
[event setValue:[RNFirebaseDatabase getJSError:error] forKey:@"error"];
[event setValue:registration forKey:@"registration"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (_emitter) {
[_emitter sendEventWithName:DATABASE_SYNC_EVENT body:event];
}
[RNFirebaseUtil sendJSEvent:self.emitter name:DATABASE_SYNC_EVENT body:event];
}
+ (NSDictionary *)snapshotToDictionary:(FIRDataSnapshot *) dataSnapshot

View File

@ -9,6 +9,7 @@
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h"
#import "RNFirebaseFirestoreDocumentReference.h"
#import "RNFirebaseUtil.h"
@interface RNFirebaseFirestoreCollectionReference : NSObject
@property RCTEventEmitter *emitter;

View File

@ -128,6 +128,9 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
if (_options[@"endBefore"]) {
query = [query queryEndingBeforeValues:_options[@"endBefore"]];
}
if (_options[@"limit"]) {
query = [query queryLimitedTo:_options[@"limit"]];
}
if (_options[@"offset"]) {
// iOS doesn't support offset
}
@ -151,11 +154,7 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
[RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
- (void)handleQuerySnapshotEvent:(NSString *)listenerId
@ -166,11 +165,7 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
[RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
+ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot {

View File

@ -9,6 +9,7 @@
#import <React/RCTEventEmitter.h>
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h"
#import "RNFirebaseUtil.h"
@interface RNFirebaseFirestoreDocumentReference : NSObject
@property RCTEventEmitter *emitter;

View File

@ -136,11 +136,7 @@ static NSMutableDictionary *_listeners;
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
[RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
- (void)handleDocumentSnapshotEvent:(NSString *)listenerId
@ -151,11 +147,7 @@ static NSMutableDictionary *_listeners;
[event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestoreDocumentReference snapshotToDictionary:documentSnapshot] forKey:@"documentSnapshot"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
[RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_DOCUMENT_SYNC_EVENT body:event];
}
@ -205,9 +197,9 @@ static NSMutableDictionary *_listeners;
typeMap[@"value"] = geopoint;
} else if ([value isKindOfClass:[NSDate class]]) {
typeMap[@"type"] = @"date";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
typeMap[@"value"] = [dateFormatter stringFromDate:(NSDate *)value];
// NOTE: The round() is important as iOS ends up giving .999 otherwise,
// and loses a millisecond when going between native and JS
typeMap[@"value"] = @(round([(NSDate *)value timeIntervalSince1970] * 1000.0));
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *number = (NSNumber *)value;
if (number == (void*)kCFBooleanFalse || number == (void*)kCFBooleanTrue) {
@ -262,9 +254,7 @@ static NSMutableDictionary *_listeners;
NSNumber *longitude = geopoint[@"longitude"];
return [[FIRGeoPoint alloc] initWithLatitude:[latitude doubleValue] longitude:[longitude doubleValue]];
} else if ([type isEqualToString:@"date"]) {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
return [dateFormatter dateFromString:value];
return [NSDate dateWithTimeIntervalSince1970:([(NSNumber *)value doubleValue] / 1000.0)];
} else if ([type isEqualToString:@"fieldvalue"]) {
NSString *string = (NSString*)value;
if ([string isEqualToString:@"delete"]) {

View File

@ -3,6 +3,7 @@
@import UserNotifications;
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <FirebaseMessaging/FirebaseMessaging.h>
#import <FirebaseInstanceID/FIRInstanceID.h>
@ -217,7 +218,7 @@ RCT_EXPORT_MODULE()
data[@"_completionHandlerId"] = completionHandlerId;
}
[self sendEventWithName:MESSAGING_NOTIFICATION_RECEIVED body:data];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_NOTIFICATION_RECEIVED body:data];
}
@ -234,13 +235,13 @@ RCT_EXPORT_MODULE()
// ** Start FIRMessagingDelegate methods **
// Handle data messages in the background
- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
[self sendEventWithName:MESSAGING_NOTIFICATION_RECEIVED body:[remoteMessage appData]];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_NOTIFICATION_RECEIVED body:[remoteMessage appData]];
}
// Listen for token refreshes
- (void)messaging:(nonnull FIRMessaging *)messaging didRefreshRegistrationToken:(nonnull NSString *)fcmToken {
NSLog(@"FCM registration token: %@", fcmToken);
[self sendEventWithName:MESSAGING_TOKEN_REFRESHED body:fcmToken];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:fcmToken];
}
// ** End FIRMessagingDelegate methods **
@ -297,7 +298,9 @@ RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(R
#endif
}
[RCTSharedApplication() registerForRemoteNotifications];
dispatch_async(dispatch_get_main_queue(), ^{
[RCTSharedApplication() registerForRemoteNotifications];
});
}
RCT_EXPORT_METHOD(subscribeToTopic: (NSString*) topic) {

View File

@ -3,6 +3,7 @@
#if __has_include(<FirebaseStorage/FIRStorage.h>)
#import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <Photos/Photos.h>
#import <Firebase.h>
@ -392,12 +393,7 @@ RCT_EXPORT_METHOD(putFile:(NSString *) appName
}
- (void)sendJSEvent:(NSString *)appName type:(NSString *)type path:(NSString *)path title:(NSString *)title props:(NSDictionary *)props {
@try {
[self sendEventWithName:type body:@{@"eventName": title, @"appName": appName, @"path": path, @"body": props}];
} @catch (NSException *err) {
NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
NSLog(@"Tried to send: %@ with %@", title, props);
}
[RNFirebaseUtil sendJSEvent:self name:type body:@{@"eventName": title, @"appName": appName, @"path": path, @"body": props}];
}
/**

View File

@ -141,18 +141,14 @@ export default class PhoneAuthListener {
_removeAllListeners() {
setTimeout(() => { // move to next event loop - not sure if needed
// internal listeners
const events = Object.values(this._internalEvents);
for (let i = 0, len = events.length; i < len; i++) {
this._auth.removeAllListeners(events[i]);
}
Object.values(this._internalEvents).forEach((event) => {
this._auth.removeAllListeners(event);
});
// user observer listeners
const publicEvents = Object.values(this._publicEvents);
for (let i = 0, len = events.length; i < len; i++) {
this._auth.removeAllListeners(publicEvents[i]);
}
Object.values(this._publicEvents).forEach((publicEvent) => {
this._auth.removeAllListeners(publicEvent);
});
}, 0);
}

View File

@ -22,33 +22,31 @@ export default class Auth extends ModuleBase {
_native: Object;
_getAppEventName: Function;
_authResult: AuthResultType | null;
authenticated: boolean;
constructor(firebaseApp: Object, options: Object = {}) {
super(firebaseApp, options, true);
this._user = null;
this._authResult = null;
this.authenticated = false;
this.addListener(
// sub to internal native event - this fans out to
// public event name: onAuthStateChanged
this._getAppEventName('auth_state_changed'),
this._onAuthStateChanged.bind(this),
this._onInternalAuthStateChanged.bind(this),
);
this.addListener(
// sub to internal native event - this fans out to
// public events based on event.type
this._getAppEventName('phone_auth_state_changed'),
this._onPhoneAuthStateChanged.bind(this),
this._onInternalPhoneAuthStateChanged.bind(this),
);
this.addListener(
// sub to internal native event - this fans out to
// public event name: onIdTokenChanged
this._getAppEventName('auth_id_token_changed'),
this._onIdTokenChanged.bind(this),
this._onInternalIdTokenChanged.bind(this),
);
this._native.addAuthStateListener();
@ -60,34 +58,25 @@ export default class Auth extends ModuleBase {
* @param event
* @private
*/
_onPhoneAuthStateChanged(event: Object) {
_onInternalPhoneAuthStateChanged(event: Object) {
const eventKey = `phone:auth:${event.requestKey}:${event.type}`;
this.emit(eventKey, event.state);
}
/**
* Internal auth changed listener
* @param auth
* @param emit
* @private
*/
_onAuthStateChanged(auth: AuthResultType, emit: boolean = true) {
_setAuthState(auth: AuthResultType) {
this._authResult = auth;
this.authenticated = auth ? auth.authenticated || false : false;
if (auth && auth.user && !this._user) this._user = new User(this, auth);
else if ((!auth || !auth.user) && this._user) this._user = null;
else if (this._user) this._user._updateValues(auth);
if (emit) this.emit(this._getAppEventName('onAuthStateChanged'), this._user);
return auth ? this._user : null;
this._user = auth && auth.user ? new User(this, auth.user) : null;
this.emit(this._getAppEventName('onUserChanged'), this._user);
}
/**
* Remove auth change listener
* @param listener
* Internal auth changed listener
* @param auth
* @private
*/
_offAuthStateChanged(listener: Function) {
this.log.info('Removing onAuthStateChanged listener');
this.removeListener(this._getAppEventName('onAuthStateChanged'), listener);
_onInternalAuthStateChanged(auth: AuthResultType) {
this._setAuthState(auth);
this.emit(this._getAppEventName('onAuthStateChanged'), this._user);
}
/**
@ -96,23 +85,9 @@ export default class Auth extends ModuleBase {
* @param emit
* @private
*/
_onIdTokenChanged(auth: AuthResultType, emit: boolean = true) {
this._authResult = auth;
this.authenticated = auth ? auth.authenticated || false : false;
if (auth && auth.user && !this._user) this._user = new User(this, auth);
else if ((!auth || !auth.user) && this._user) this._user = null;
else if (this._user) this._user._updateValues(auth);
if (emit) this.emit(this._getAppEventName('onIdTokenChanged'), this._user);
return auth ? this._user : null;
}
/**
* Remove id token change listener
* @param listener
*/
_offIdTokenChanged(listener: Function) {
this.log.info('Removing onIdTokenChanged listener');
this.removeListener(this._getAppEventName('onIdTokenChanged'), listener);
_onInternalIdTokenChanged(auth: AuthResultType) {
this._setAuthState(auth);
this.emit(this._getAppEventName('onIdTokenChanged'), this._user);
}
/**
@ -124,10 +99,10 @@ export default class Auth extends ModuleBase {
*/
_interceptUserValue(promise) {
return promise.then((result) => {
if (!result) return this._onAuthStateChanged(null, false);
if (result.user) return this._onAuthStateChanged(result, false);
if (result.uid) return this._onAuthStateChanged({ authenticated: true, user: result }, false);
return result;
if (!result) this._setAuthState(null);
else if (result.user) this._setAuthState(result);
else if (result.uid) this._setAuthState({ authenticated: true, user: result });
return this._user;
});
}
@ -146,6 +121,15 @@ export default class Auth extends ModuleBase {
return this._offAuthStateChanged.bind(this, listener);
}
/**
* Remove auth change listener
* @param listener
*/
_offAuthStateChanged(listener: Function) {
this.log.info('Removing onAuthStateChanged listener');
this.removeListener(this._getAppEventName('onAuthStateChanged'), listener);
}
/**
* Listen for id token changes.
* @param listener
@ -157,6 +141,35 @@ export default class Auth extends ModuleBase {
return this._offIdTokenChanged.bind(this, listener);
}
/**
* Remove id token change listener
* @param listener
*/
_offIdTokenChanged(listener: Function) {
this.log.info('Removing onIdTokenChanged listener');
this.removeListener(this._getAppEventName('onIdTokenChanged'), listener);
}
/**
* Listen for user changes.
* @param listener
*/
onUserChanged(listener: Function) {
this.log.info('Creating onUserChanged listener');
this.on(this._getAppEventName('onUserChanged'), listener);
if (this._authResult) listener(this._user || null);
return this._offUserChanged.bind(this, listener);
}
/**
* Remove user change listener
* @param listener
*/
_offUserChanged(listener: Function) {
this.log.info('Removing onUserChanged listener');
this.removeListener(this._getAppEventName('onUserChanged'), listener);
}
/**
* Sign the current user out
* @return {Promise}

View File

@ -7,32 +7,17 @@ export default class User {
/**
*
* @param authClass Instance of Authentication class
* @param authObj authentication result object from native
* @param user user result object from native
*/
constructor(authClass, authObj) {
constructor(authClass, userObj) {
this._auth = authClass;
this._user = null;
this._updateValues(authObj);
this._user = userObj;
}
/**
* INTERNALS
*/
/**
*
* @param authObj
* @private
*/
_updateValues(authObj) {
this._authObj = authObj;
if (authObj.user) {
this._user = authObj.user;
} else {
this._user = null;
}
}
/**
* Returns a user property or null if does not exist
* @param prop
@ -40,7 +25,6 @@ export default class User {
* @private
*/
_valueOrNull(prop) {
if (!this._user) return null;
if (!Object.hasOwnProperty.call(this._user, prop)) return null;
return this._user[prop];
}
@ -52,7 +36,6 @@ export default class User {
* @private
*/
_valueOrFalse(prop) {
if (!this._user) return false;
if (!Object.hasOwnProperty.call(this._user, prop)) return false;
return this._user[prop];
}

View File

@ -26,6 +26,7 @@ export default class Query {
*/
orderBy(name: string, key?: string) {
this.modifiers.push({
id: `orderBy-${name}:${key}`,
type: 'orderBy',
name,
key,
@ -42,6 +43,7 @@ export default class Query {
*/
limit(name: string, limit: number) {
this.modifiers.push({
id: `limit-${name}:${limit}`,
type: 'limit',
name,
limit,
@ -59,6 +61,7 @@ export default class Query {
*/
filter(name: string, value: any, key?: string) {
this.modifiers.push({
id: `filter-${name}:${objectToUniqueId(value)}:${key}`,
type: 'filter',
name,
value,
@ -82,14 +85,21 @@ export default class Query {
* @return {*}
*/
queryIdentifier() {
// convert query modifiers array into an object for generating a unique key
const object = {};
// sort modifiers to enforce ordering
const sortedModifiers = this.getModifiers().sort((a, b) => {
if (a.id < b.id) return -1;
if (a.id > b.id) return 1;
return 0;
});
for (let i = 0, len = this.modifiers.length; i < len; i++) {
const { name, type, value } = this.modifiers[i];
object[`${type}-${name}`] = value;
// Convert modifiers to unique key
let key = '{';
for (let i = 0; i < sortedModifiers.length; i++) {
if (i !== 0) key += ',';
key += sortedModifiers[i].id;
}
key += '}';
return objectToUniqueId(object);
return key;
}
}

View File

@ -736,14 +736,15 @@ export default class Reference extends ReferenceBase {
// remove the callback.
// Remove only a single registration
if (eventType && originalCallback) {
const registrations = this._syncTree.getRegistrationsByPathEvent(this.path, eventType);
const registration = this._syncTree.getOneByPathEventListener(this.path, eventType, originalCallback);
if (!registration) return [];
// remove the paired cancellation registration if any exist
this._syncTree.removeListenersForRegistrations([`${registrations[0]}$cancelled`]);
this._syncTree.removeListenersForRegistrations([`${registration}$cancelled`]);
// remove only the first registration to match firebase web sdk
// call multiple times to remove multiple registrations
return this._syncTree.removeListenerRegistrations(originalCallback, [registrations[0]]);
return this._syncTree.removeListenerRegistrations(originalCallback, [registration]);
}
// Firebase Docs:

View File

@ -1,12 +1,12 @@
/**
* @flow
* Database representation wrapper
* Database Transaction representation wrapper
*/
let transactionId = 0;
/**
* @class Database
* @class TransactionHandler
*/
export default class TransactionHandler {
constructor(database: Object) {

View File

@ -67,7 +67,7 @@ const buildTypeMap = (value: any): any => {
};
} else if (value instanceof Date) {
typeMap.type = 'date';
typeMap.value = value.toISOString();
typeMap.value = value.getTime();
} else {
typeMap.type = 'object';
typeMap.value = buildNativeMap(value);

View File

@ -11,6 +11,7 @@ type Registration = {
once?: Boolean,
appName: String,
eventType: String,
listener: Function,
eventRegistrationKey: String,
ref: DatabaseReference,
}
@ -197,6 +198,28 @@ export default class SyncTree {
return Object.keys(this._tree[path][eventType]);
}
/**
* Returns a single registration key for the specified path, eventType, and listener
*
* @param path
* @param eventType
* @param listener
* @return {Array}
*/
getOneByPathEventListener(path: string, eventType: string, listener: Function): Array {
if (!this._tree[path]) return [];
if (!this._tree[path][eventType]) return [];
const registrationsForPathEvent = Object.entries(this._tree[path][eventType]);
for (let i = 0; i < registrationsForPathEvent.length; i++) {
const registration = registrationsForPathEvent[i];
if (registration[1] === listener) return registration[0];
}
return null;
}
/**
* Register a new listener.
@ -211,8 +234,8 @@ export default class SyncTree {
if (!this._tree[path]) this._tree[path] = {};
if (!this._tree[path][eventType]) this._tree[path][eventType] = {};
this._tree[path][eventType][eventRegistrationKey] = 0;
this._reverseLookup[eventRegistrationKey] = Object.assign({}, parameters);
this._tree[path][eventType][eventRegistrationKey] = listener;
this._reverseLookup[eventRegistrationKey] = Object.assign({ listener }, parameters);
if (once) {
INTERNALS.SharedEventEmitter.once(

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "react-native-firebase",
"version": "3.0.4",
"version": "3.0.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -150,7 +150,7 @@ PODS:
- React/Core
- React/fishhook
- React/RCTBlob
- RNFirebase (3.0.0):
- RNFirebase (3.0.5):
- React
- yoga (0.49.1.React)
@ -208,7 +208,7 @@ SPEC CHECKSUMS:
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8
React: cf892fb84b7d06bf5fea7f328e554c6dcabe85ee
RNFirebase: 901a473c68fcbaa28125c56a911923f2fbe5d61b
RNFirebase: 7c86b4efd2860700048d927f34db237fbce1d5fc
yoga: 3abf02d6d9aeeb139b4c930eb1367feae690a35a
PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45

View File

@ -1,4 +1,6 @@
import should from 'should';
import sinon from 'sinon';
import 'should-sinon';
import DatabaseContents from '../../support/DatabaseContents';
function issueTests({ describe, it, context, firebase }) {
@ -81,6 +83,212 @@ function issueTests({ describe, it, context, firebase }) {
});
});
});
describe('issue_489', () => {
context('long numbers should', () => {
it('return as longs', async () => {
// Setup
const long1Ref = firebase.native.database().ref('tests/issues/489/long1');
const long2Ref = firebase.native.database().ref('tests/issues/489/long2');
const long2 = 1234567890123456;
// Test
let snapshot = await long1Ref.once('value');
snapshot.val().should.eql(DatabaseContents.ISSUES[489].long1);
await long2Ref.set(long2);
snapshot = await long2Ref.once('value');
snapshot.val().should.eql(long2);
return Promise.resolve();
});
});
});
describe('issue_521', () => {
context('orderByChild (numerical field) and limitToLast', () => {
it('once() returns correct results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521');
// Test
return ref
.orderByChild('number')
.limitToLast(1)
.once('value')
.then((snapshot) => {
const val = snapshot.val();
// Assertion
val.key3.should.eql(DatabaseContents.ISSUES[521].key3);
should.equal(Object.keys(val).length, 1);
return Promise.resolve();
});
});
it('on() returns correct initial results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('number').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
return Promise.resolve();
});
it('on() returns correct subsequent results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('number').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
const newDataValue = {
name: 'Item 4',
number: 4,
string: 'item4',
};
const newRef = firebase.native.database().ref('tests/issues/521/key4');
await newRef.set(newDataValue);
await new Promise((resolve) => {
setTimeout(() => resolve(), 5);
});
// Assertions
callback.should.be.calledWith({
key3: DatabaseContents.ISSUES[521].key3,
key4: newDataValue,
});
callback.should.be.calledTwice();
return Promise.resolve();
});
});
context('orderByChild (string field) and limitToLast', () => {
it('once() returns correct results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521');
// Test
return ref
.orderByChild('string')
.limitToLast(1)
.once('value')
.then((snapshot) => {
const val = snapshot.val();
// Assertion
val.key3.should.eql(DatabaseContents.ISSUES[521].key3);
should.equal(Object.keys(val).length, 1);
return Promise.resolve();
});
});
it('on() returns correct initial results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('string').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
return Promise.resolve();
});
it('on() returns correct subsequent results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('string').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
const newDataValue = {
name: 'Item 4',
number: 4,
string: 'item4',
};
const newRef = firebase.native.database().ref('tests/issues/521/key4');
await newRef.set(newDataValue);
await new Promise((resolve) => {
setTimeout(() => resolve(), 5);
});
// Assertions
callback.should.be.calledWith({
key3: DatabaseContents.ISSUES[521].key3,
key4: newDataValue,
});
callback.should.be.calledTwice();
return Promise.resolve();
});
});
});
}
export default issueTests;

View File

@ -297,6 +297,167 @@ function offTests({ describe, it, xcontext, context, firebase }) {
});
});
context('when 2 different child_added callbacks on the same path', () => {
context('that has been added and removed in the same order', () => {
it('must be completely removed', async () => {
// Setup
const spyA = sinon.spy();
let callbackA;
const spyB = sinon.spy();
let callbackB;
const ref = firebase.native.database().ref('tests/types/array');
const arrayLength = DatabaseContents.DEFAULT.array.length;
// Attach callbackA
await new Promise((resolve) => {
callbackA = () => {
spyA();
resolve();
};
ref.on('child_added', callbackA);
});
// Attach callbackB
await new Promise((resolve) => {
callbackB = () => {
spyB();
resolve();
};
ref.on('child_added', callbackB);
});
// Add a delay to ensure that the .on() has had time to be registered
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 15);
});
spyA.should.have.callCount(arrayLength);
spyB.should.have.callCount(arrayLength);
// Undo the first callback
const resp = await ref.off('child_added', callbackA);
should(resp, undefined);
// Trigger the event the callback is listening to
await ref.push(DatabaseContents.DEFAULT.number);
// Add a delay to ensure that the .set() has had time to be registered
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 15);
});
// CallbackA should have been called zero more times its attachment
// has been removed, and callBackB only one more time becuase it's still attached
spyA.should.have.callCount(arrayLength);
spyB.should.have.callCount(arrayLength + 1);
// Undo the second attachment
const resp2 = await ref.off('child_added', callbackB);
should(resp2, undefined);
// Trigger the event the callback is listening to
await ref.push(DatabaseContents.DEFAULT.number);
// Add a delay to ensure that the .set() has had time to be registered
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 15);
});
// Both Callbacks should not have been called any more times
spyA.should.have.callCount(arrayLength);
spyB.should.have.callCount(arrayLength + 1);
});
});
// ******This test is failed*******
context('that has been added and removed in reverse order', () => {
it('must be completely removed', async () => {
// Setup
const spyA = sinon.spy();
let callbackA;
const spyB = sinon.spy();
let callbackB;
const ref = firebase.native.database().ref('tests/types/array');
const arrayLength = DatabaseContents.DEFAULT.array.length;
// Attach callbackA
await new Promise((resolve) => {
callbackA = () => {
spyA();
resolve();
};
ref.on('child_added', callbackA);
});
// Attach callbackB
await new Promise((resolve) => {
callbackB = () => {
spyB();
resolve();
};
ref.on('child_added', callbackB);
});
// Add a delay to ensure that the .on() has had time to be registered
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 15);
});
spyA.should.have.callCount(arrayLength);
spyB.should.have.callCount(arrayLength);
// Undo the second callback
const resp = await ref.off('child_added', callbackB);
should(resp, undefined);
// Trigger the event the callback is listening to
await ref.push(DatabaseContents.DEFAULT.number);
// Add a delay to ensure that the .set() has had time to be registered
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 15);
});
// CallbackB should have been called zero more times its attachment
// has been removed, and callBackA only one more time becuase it's still attached
spyA.should.have.callCount(arrayLength + 1);
spyB.should.have.callCount(arrayLength);
// Undo the second attachment
const resp2 = await ref.off('child_added', callbackA);
should(resp2, undefined);
// Trigger the event the callback is listening to
await ref.push(DatabaseContents.DEFAULT.number);
// Add a delay to ensure that the .set() has had time to be registered
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 15);
});
// Both Callbacks should not have been called any more times
spyA.should.have.callCount(arrayLength + 1);
spyB.should.have.callCount(arrayLength);
});
});
});
xcontext('when a context', () => {
/**
* @todo Add tests for when a context is passed. Not sure what the intended

View File

@ -435,6 +435,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
const doc = await docRef.get();
doc.data().field.should.be.instanceof(Date);
should.equal(doc.data().field.toISOString(), date.toISOString());
should.equal(doc.data().field.getTime(), date.getTime());
});
});

View File

@ -91,5 +91,28 @@ export default {
uid: 'aNYxLexOb2WsXGOPiEAu47q5bxH3',
},
},
489: {
long1: 1508777379000,
},
// https://github.com/invertase/react-native-firebase/issues/521
521: {
key1: {
name: 'Item 1',
number: 1,
string: 'item1',
},
key3: {
name: 'Item 3',
number: 3,
string: 'item3',
},
key2: {
name: 'Item 2',
number: 2,
string: 'item2',
},
},
},
};