Merge remote-tracking branch 'origin/master'

# Conflicts:
#	tests/ios/Podfile.lock
This commit is contained in:
Salakar 2017-11-03 23:28:09 +00:00
commit 868075d479
43 changed files with 1193 additions and 555 deletions

View File

@ -36,3 +36,6 @@
<!--- (e.g. database, auth, messaging, analytics etc - or N/A if not applicable) ---> <!--- (e.g. database, auth, messaging, analytics etc - or N/A if not applicable) --->
6. Firebase Module: 6. Firebase Module:
<!-- Love react-native-firebase? Please consider supporting our collective:
👉 https://opencollective.com/react-native-firebase/donate -->

67
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,67 @@
# Contribute
## Introduction
First, thank you for considering contributing to react-native-firebase! It's people like you that make the open source community such a great community! 😊
We welcome any type of contribution, not only code. You can help with
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, printing stickers, ...
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/react-native-firebase).
## Your First Contribution
Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
## Code review process
The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/react-native-firebase).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
## Questions
If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
You can also reach us at oss@invertase.io
## Credits
### Contributors
Thank you to all the people who have already contributed to react-native-firebase!
<a href="graphs/contributors"><img src="https://opencollective.com/react-native-firebase/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/react-native-firebase#backer)]
<a href="https://opencollective.com/react-native-firebase#backers" target="_blank"><img src="https://opencollective.com/react-native-firebase/backers.svg?width=890"></a>
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/react-native-firebase#sponsor))
<a href="https://opencollective.com/react-native-firebase/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/9/avatar.svg"></a>
<!-- This `CONTRIBUTING.md` is based on @nayafia's template https://github.com/nayafia/contributing-template -->

View File

@ -1,8 +1,7 @@
# React Native Firebase<a href="https://rnfirebase.io"><img align="left" src="http://i.imgur.com/01XQL0x.png"></a> # React Native Firebase<a href="https://rnfirebase.io"><img align="left" src="http://i.imgur.com/01XQL0x.png"></a>
[![npm version](https://img.shields.io/npm/v/react-native-firebase.svg?style=flat-square)](https://www.npmjs.com/package/react-native-firebase) [![Backers on Open Collective](https://opencollective.com/react-native-firebase/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/react-native-firebase/sponsors/badge.svg)](#sponsors) [![npm version](https://img.shields.io/npm/v/react-native-firebase.svg?style=flat-square)](https://www.npmjs.com/package/react-native-firebase)
[![NPM downloads](https://img.shields.io/npm/dm/react-native-firebase.svg?style=flat-square)](https://www.npmjs.com/package/react-native-firebase) [![NPM downloads](https://img.shields.io/npm/dm/react-native-firebase.svg?style=flat-square)](https://www.npmjs.com/package/react-native-firebase)
[![Package Quality](http://npm.packagequality.com/shield/react-native-firebase.svg?style=flat-square)](http://packagequality.com/#?package=react-native-firebase)
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs) [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?style=flat-square)](https://discord.gg/t6bdqMs)
[![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase) [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](https://www.patreon.com/invertase)
[![Twitter Follow](https://img.shields.io/twitter/follow/rnfirebase.svg?style=social&label=Follow)](https://twitter.com/rnfirebase) [![Twitter Follow](https://img.shields.io/twitter/follow/rnfirebase.svg?style=social&label=Follow)](https://twitter.com/rnfirebase)
@ -53,40 +52,70 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
> '**?**' indicates partial support > '**?**' indicates partial support
| Firebase Features | v1.x.x | v2.x.x | v3.x.x | Web SDK | | Firebase Features | v1.x.x | v2.x.x | v3.x.x | v3.1.x | Web SDK |
| ---------------------- | :---: | :---: | :---: | :---: | | ---------------------- | :---: | :---: | :---: | :---: | :---: |
| **AdMob** | ❌ | ✅ | ✅ | ❌ | | **AdMob** | ❌ | ✅ | ✅ | ✅ | ❌ |
| **Analytics**             | ✅ | ✅ | ✅ | ❌ | | **Analytics**             | ✅ | ✅ | ✅ | ✅ | ❌ |
| **App Indexing**           | ❌ | ❌ | ❌ | ❌ | | **App Indexing**           | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Authentication** | ✅ | ✅ | ✅ | ✅ | | **Authentication** | ✅ | ✅ | ✅ | ✅ | ✅ |
| _-- Phone Auth_ | ❌ | ❌ | ✅ | ❌ | | _-- Phone Auth_ | ❌ | ❌ | ✅ | ✅ | ❌ |
| **Core** | ❌ |**?**| ✅ | ✅ | | **Core** | ❌ |**?**| ✅ | ✅ | ✅ |
| _-- Multiple Apps_ | ❌ | ❌ | ✅ | ✅ | | _-- Multiple Apps_ | ❌ | ❌ | ✅ | ✅ | ✅ |
| **Cloud Firestore** | ❌ | ❌ | ✅ | ❌ | | **Cloud Firestore** | ❌ | ❌ | ✅ | ✅ | ❌ |
| **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ |**?**| | **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ | ✅ |**?**|
| **Crash Reporting** | ✅ | ✅ | ✅ | ❌ | | **Crash Reporting** | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Dynamic Links** | ❌ | ❌ | ❌ | ❌ | | **Dynamic Links** | ❌ | ❌ | ❌ | ✅ | ❌ |
| **Invites** | ❌ | ❌ | ❌ | ❌ | | **Invites** | ❌ | ❌ | ❌ |**?**| ❌ |
| **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ | | **Performance Monitoring** | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Realtime Database** | ✅ | ✅ | ✅ | ✅ | | **Realtime Database** | ✅ | ✅ | ✅ | ✅ | ✅ |
| _-- Offline Persistence_ | ✅ | ✅ | ✅ |**?**| | _-- Offline Persistence_ | ✅ | ✅ | ✅ | ✅ |**?**|
| _-- Transactions_ | ✅ | ✅ | ✅ | ✅ | | _-- Transactions_ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Remote Config** | ✅ | ✅ | ✅ | ❌ | | **Remote Config** | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Storage** | ✅ | ✅ | ✅ |**?**| | **Storage** | ✅ | ✅ | ✅ | ✅ |**?**|
--- ---
### Supported versions - React Native / Firebase ### Supported versions - React Native / Firebase
> The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase` > The table below shows the supported versions of React Native and the Firebase SDKs for different versions of `react-native-firebase`
| | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X | | | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X | 3.1.X |
|------------------------|-------------|-------------|-----------------|----------| |------------------------|-------------|-------------|-----------------|----------|----------|
| React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + | | React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + | 0.48 + |
| Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + | | Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + | 11.4.2 + |
| Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + | | Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + | 4.5.0 + |
--- ---
## Contributors
This project exists thanks to all the people who contribute. [[Contribute]](CONTRIBUTING.md).
<a href="graphs/contributors"><img src="https://opencollective.com/react-native-firebase/contributors.svg?width=890" /></a>
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/react-native-firebase#backer)]
<a href="https://opencollective.com/react-native-firebase#backers" target="_blank"><img src="https://opencollective.com/react-native-firebase/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/react-native-firebase#sponsor)]
<a href="https://opencollective.com/react-native-firebase/sponsor/0/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/1/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/2/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/3/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/4/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/5/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/6/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/7/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/8/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/react-native-firebase/sponsor/9/website" target="_blank"><img src="https://opencollective.com/react-native-firebase/sponsor/9/avatar.svg"></a>
### License ### License
- See [LICENSE](/LICENSE) - See [LICENSE](/LICENSE)

View File

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

View File

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

View File

@ -31,8 +31,6 @@ import io.invertase.firebase.Utils;
public class FirestoreSerialize { public class FirestoreSerialize {
private static final String TAG = "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_CHANGES = "changes";
private static final String KEY_DATA = "data"; private static final String KEY_DATA = "data";
private static final String KEY_DOC_CHANGE_DOCUMENT = "document"; 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_METADATA = "metadata";
private static final String KEY_PATH = "path"; 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 * Convert a DocumentSnapshot instance into a React Native WritableMap
* *
@ -220,7 +212,7 @@ public class FirestoreSerialize {
typeMap.putMap("value", geoPoint); typeMap.putMap("value", geoPoint);
} else if (value instanceof Date) { } else if (value instanceof Date) {
typeMap.putString("type", "date"); typeMap.putString("type", "date");
typeMap.putString("value", WRITE_DATE_FORMAT.format((Date) value)); typeMap.putDouble("value", ((Date) value).getTime());
} else { } else {
// TODO: Changed to log an error rather than crash - is this correct? // TODO: Changed to log an error rather than crash - is this correct?
Log.e(TAG, "buildTypeMap: Cannot convert object of type " + value.getClass()); Log.e(TAG, "buildTypeMap: Cannot convert object of type " + value.getClass());
@ -244,7 +236,7 @@ public class FirestoreSerialize {
return map; return map;
} }
private static List<Object> parseReadableArray(FirebaseFirestore firestore, ReadableArray readableArray) { static List<Object> parseReadableArray(FirebaseFirestore firestore, ReadableArray readableArray) {
List<Object> list = new ArrayList<>(); List<Object> list = new ArrayList<>();
if (readableArray != null) { if (readableArray != null) {
for (int i = 0; i < readableArray.size(); i++) { for (int i = 0; i < readableArray.size(); i++) {
@ -254,7 +246,7 @@ public class FirestoreSerialize {
return list; return list;
} }
private static Object parseTypeMap(FirebaseFirestore firestore, ReadableMap typeMap) { static Object parseTypeMap(FirebaseFirestore firestore, ReadableMap typeMap) {
String type = typeMap.getString("type"); String type = typeMap.getString("type");
if ("boolean".equals(type)) { if ("boolean".equals(type)) {
return typeMap.getBoolean("value"); return typeMap.getBoolean("value");
@ -275,13 +267,8 @@ public class FirestoreSerialize {
ReadableMap geoPoint = typeMap.getMap("value"); ReadableMap geoPoint = typeMap.getMap("value");
return new GeoPoint(geoPoint.getDouble("latitude"), geoPoint.getDouble("longitude")); return new GeoPoint(geoPoint.getDouble("latitude"), geoPoint.getDouble("longitude"));
} else if ("date".equals(type)) { } else if ("date".equals(type)) {
try { Double time = typeMap.getDouble("value");
String date = typeMap.getString("value"); return new Date(time.longValue());
return READ_DATE_FORMAT.parse(date);
} catch (ParseException exception) {
Log.e(TAG, "parseTypeMap", exception);
return null;
}
} else if ("fieldvalue".equals(type)) { } else if ("fieldvalue".equals(type)) {
String value = typeMap.getString("value"); String value = typeMap.getString("value");
if ("delete".equals(value)) { if ("delete".equals(value)) {

View File

@ -14,6 +14,7 @@ import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.DocumentListenOptions; import com.google.firebase.firestore.DocumentListenOptions;
import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.ListenerRegistration; import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.Query; import com.google.firebase.firestore.Query;
@ -115,22 +116,22 @@ public class RNFirebaseFirestoreCollectionReference {
} }
private Query buildQuery() { private Query buildQuery() {
Query query = RNFirebaseFirestore.getFirestoreForApp(appName).collection(path); FirebaseFirestore firestore = RNFirebaseFirestore.getFirestoreForApp(appName);
query = applyFilters(query); Query query = firestore.collection(path);
query = applyFilters(firestore, query);
query = applyOrders(query); query = applyOrders(query);
query = applyOptions(query); query = applyOptions(firestore, query);
return query; return query;
} }
private Query applyFilters(Query query) { private Query applyFilters(FirebaseFirestore firestore, Query query) {
List<Object> filtersList = Utils.recursivelyDeconstructReadableArray(filters); for (int i = 0; i < filters.size(); i++) {
ReadableMap filter = filters.getMap(i);
for (Object f : filtersList) { String fieldPath = filter.getString("fieldPath");
Map<String, Object> filter = (Map) f; String operator = filter.getString("operator");
String fieldPath = (String) filter.get("fieldPath"); ReadableMap jsValue = filter.getMap("value");
String operator = (String) filter.get("operator"); Object value = FirestoreSerialize.parseTypeMap(firestore, jsValue);
Object value = filter.get("value");
switch (operator) { switch (operator) {
case "EQUAL": case "EQUAL":
@ -165,14 +166,14 @@ public class RNFirebaseFirestoreCollectionReference {
return query; return query;
} }
private Query applyOptions(Query query) { private Query applyOptions(FirebaseFirestore firestore, Query query) {
if (options.hasKey("endAt")) { if (options.hasKey("endAt")) {
ReadableArray endAtArray = options.getArray("endAt"); List<Object> endAtList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endAt"));
query = query.endAt(Utils.recursivelyDeconstructReadableArray(endAtArray)); query = query.endAt(endAtList.toArray());
} }
if (options.hasKey("endBefore")) { if (options.hasKey("endBefore")) {
ReadableArray endBeforeArray = options.getArray("endBefore"); List<Object> endBeforeList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endBefore"));
query = query.endBefore(Utils.recursivelyDeconstructReadableArray(endBeforeArray)); query = query.endBefore(endBeforeList.toArray());
} }
if (options.hasKey("limit")) { if (options.hasKey("limit")) {
int limit = options.getInt("limit"); int limit = options.getInt("limit");
@ -185,12 +186,12 @@ public class RNFirebaseFirestoreCollectionReference {
// Android doesn't support selectFields // Android doesn't support selectFields
} }
if (options.hasKey("startAfter")) { if (options.hasKey("startAfter")) {
ReadableArray startAfterArray = options.getArray("startAfter"); List<Object> startAfterList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAfter"));
query = query.startAfter(Utils.recursivelyDeconstructReadableArray(startAfterArray)); query = query.startAfter(startAfterList.toArray());
} }
if (options.hasKey("startAt")) { if (options.hasKey("startAt")) {
ReadableArray startAtArray = options.getArray("startAt"); List<Object> startAtList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAt"));
query = query.startAt(Utils.recursivelyDeconstructReadableArray(startAtArray)); query = query.startAt(startAtList.toArray());
} }
return query; return query;
} }

View File

@ -12,8 +12,6 @@ import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import android.net.Uri; import android.net.Uri;
import android.database.Cursor;
import android.provider.MediaStore;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
@ -339,14 +337,7 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
Log.i(TAG, "putFile: " + localPath + " to " + path); Log.i(TAG, "putFile: " + localPath + " to " + path);
try { try {
Uri file; Uri file = getURI(localPath);
if (localPath.startsWith("content://")) {
String realPath = getRealPathFromURI(localPath);
file = Uri.fromFile(new File(realPath));
} else {
file = Uri.fromFile(new File(localPath));
}
StorageMetadata md = buildMetadataFromMap(metadata); StorageMetadata md = buildMetadataFromMap(metadata);
UploadTask uploadTask = reference.putFile(file, md); UploadTask uploadTask = reference.putFile(file, md);
@ -415,24 +406,18 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
} }
/** /**
* Internal helper to convert content:// uri's to a real path * Create a Uri from the path, defaulting to file when there is no supplied scheme
* *
* @param uri * @param uri
* @return * @return
*/ */
private String getRealPathFromURI(final String uri) { private Uri getURI(final String uri) {
Cursor cursor = null; Uri parsed = Uri.parse(uri);
try {
String[] proj = {MediaStore.Images.Media.DATA}; if (parsed.getScheme() == null || parsed.getScheme().isEmpty()) {
cursor = getReactApplicationContext().getContentResolver().query(Uri.parse(uri), proj, null, null, null); return Uri.fromFile(new File(uri));
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
} }
return parsed;
} }
/** /**

View File

@ -26,7 +26,8 @@
839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91621EF3E20A0077C7C8 /* RNFirebaseDatabase.m */; }; 839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91621EF3E20A0077C7C8 /* RNFirebaseDatabase.m */; };
839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */; }; 839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */; };
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.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 */; };
BA84AE571FA9E59800E79390 /* RNFirebaseStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = BA84AE561FA9E59800E79390 /* RNFirebaseStorage.m */; };
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = D950369D1D19C77400F7094D /* RNFirebase.m */; }; D950369E1D19C77400F7094D /* RNFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = D950369D1D19C77400F7094D /* RNFirebase.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -82,9 +83,11 @@
839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseMessaging.m; sourceTree = "<group>"; }; 839D91651EF3E20A0077C7C8 /* RNFirebaseMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseMessaging.m; sourceTree = "<group>"; };
839D91671EF3E20A0077C7C8 /* RNFirebasePerformance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebasePerformance.h; sourceTree = "<group>"; }; 839D91671EF3E20A0077C7C8 /* RNFirebasePerformance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebasePerformance.h; sourceTree = "<group>"; };
839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebasePerformance.m; sourceTree = "<group>"; }; 839D91681EF3E20A0077C7C8 /* RNFirebasePerformance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebasePerformance.m; sourceTree = "<group>"; };
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>"; }; 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>"; };
BA84AE551FA9E59800E79390 /* RNFirebaseStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseStorage.h; sourceTree = "<group>"; };
BA84AE561FA9E59800E79390 /* RNFirebaseStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseStorage.m; sourceTree = "<group>"; };
D950369C1D19C77400F7094D /* RNFirebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNFirebase.h; path = RNFirebase/RNFirebase.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>"; }; D950369D1D19C77400F7094D /* RNFirebase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNFirebase.m; path = RNFirebase/RNFirebase.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -121,6 +124,7 @@
58B511D21A9E6C8500147676 = { 58B511D21A9E6C8500147676 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BA84AE541FA9E59800E79390 /* storage */,
17AF4F681F59CDBF00C02336 /* links */, 17AF4F681F59CDBF00C02336 /* links */,
839D914D1EF3E20A0077C7C8 /* admob */, 839D914D1EF3E20A0077C7C8 /* admob */,
839D91541EF3E20A0077C7C8 /* analytics */, 839D91541EF3E20A0077C7C8 /* analytics */,
@ -131,11 +135,12 @@
8376F70D1F7C141500D45A85 /* firestore */, 8376F70D1F7C141500D45A85 /* firestore */,
839D91631EF3E20A0077C7C8 /* messaging */, 839D91631EF3E20A0077C7C8 /* messaging */,
839D91661EF3E20A0077C7C8 /* perf */, 839D91661EF3E20A0077C7C8 /* perf */,
134814211AA4EA7D00B7C361 /* Products */,
D950369C1D19C77400F7094D /* RNFirebase.h */, D950369C1D19C77400F7094D /* RNFirebase.h */,
D950369D1D19C77400F7094D /* RNFirebase.m */, D950369D1D19C77400F7094D /* RNFirebase.m */,
839D91771EF3E22F0077C7C8 /* RNFirebaseEvents.h */, 839D91771EF3E22F0077C7C8 /* RNFirebaseEvents.h */,
839D91691EF3E20A0077C7C8 /* storage */, 83C3EEED1FA1EACC00B64D3C /* RNFirebaseUtil.h */,
83C3EEEC1FA1EACC00B64D3C /* RNFirebaseUtil.m */,
134814211AA4EA7D00B7C361 /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -247,11 +252,11 @@
path = RNFirebase/perf; path = RNFirebase/perf;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
839D91691EF3E20A0077C7C8 /* storage */ = { BA84AE541FA9E59800E79390 /* storage */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
839D916A1EF3E20A0077C7C8 /* RNFirebaseStorage.h */, BA84AE551FA9E59800E79390 /* RNFirebaseStorage.h */,
839D916B1EF3E20A0077C7C8 /* RNFirebaseStorage.m */, BA84AE561FA9E59800E79390 /* RNFirebaseStorage.m */,
); );
name = storage; name = storage;
path = RNFirebase/storage; path = RNFirebase/storage;
@ -317,7 +322,6 @@
839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */, 839D916C1EF3E20B0077C7C8 /* RNFirebaseAdMob.m in Sources */,
17AF4F6B1F59CDBF00C02336 /* RNFirebaseLinks.m in Sources */, 17AF4F6B1F59CDBF00C02336 /* RNFirebaseLinks.m in Sources */,
8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */, 8376F7161F7C149100D45A85 /* RNFirebaseFirestoreCollectionReference.m in Sources */,
839D91761EF3E20B0077C7C8 /* RNFirebaseStorage.m in Sources */,
8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */, 8376F7151F7C149100D45A85 /* RNFirebaseFirestore.m in Sources */,
839D91701EF3E20B0077C7C8 /* RNFirebaseAuth.m in Sources */, 839D91701EF3E20B0077C7C8 /* RNFirebaseAuth.m in Sources */,
8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */, 8323CF091F6FBD870071420B /* RNFirebaseAdMobNativeExpressManager.m in Sources */,
@ -326,7 +330,9 @@
839D91711EF3E20B0077C7C8 /* RNFirebaseRemoteConfig.m in Sources */, 839D91711EF3E20B0077C7C8 /* RNFirebaseRemoteConfig.m in Sources */,
D950369E1D19C77400F7094D /* RNFirebase.m in Sources */, D950369E1D19C77400F7094D /* RNFirebase.m in Sources */,
839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */, 839D91731EF3E20B0077C7C8 /* RNFirebaseDatabase.m in Sources */,
BA84AE571FA9E59800E79390 /* RNFirebaseStorage.m in Sources */,
8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */, 8323CF071F6FBD870071420B /* NativeExpressComponent.m in Sources */,
83C3EEEE1FA1EACC00B64D3C /* RNFirebaseUtil.m in Sources */,
839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */, 839D91721EF3E20B0077C7C8 /* RNFirebaseCrash.m in Sources */,
839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */, 839D91741EF3E20B0077C7C8 /* RNFirebaseMessaging.m in Sources */,
839D91751EF3E20B0077C7C8 /* RNFirebasePerformance.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 "RNFirebaseAdMobInterstitial.h"
#import "RNFirebaseUtil.h"
@implementation RNFirebaseAdMobInterstitial @implementation RNFirebaseAdMobInterstitial
@ -31,7 +32,7 @@
} }
- (void)sendJSEvent:(NSString *)type payload:(NSDictionary *)payload { - (void)sendJSEvent:(NSString *)type payload:(NSDictionary *)payload {
[_delegate sendEventWithName:ADMOB_INTERSTITIAL_EVENT body:@{ [RNFirebaseUtil sendJSEvent:self.delegate name:ADMOB_INTERSTITIAL_EVENT body:@{
@"type": type, @"type": type,
@"adUnit": _adUnitID, @"adUnit": _adUnitID,
@"payload": payload @"payload": payload

View File

@ -1,4 +1,5 @@
#import "RNFirebaseAdMobRewardedVideo.h" #import "RNFirebaseAdMobRewardedVideo.h"
#import "RNFirebaseUtil.h"
@implementation RNFirebaseAdMobRewardedVideo @implementation RNFirebaseAdMobRewardedVideo
@ -31,7 +32,7 @@
} }
- (void)sendJSEvent:(NSString *)type payload:(NSDictionary *)payload { - (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, @"type": type,
@"adUnit": _adUnitID, @"adUnit": _adUnitID,
@"payload": payload @"payload": payload

View File

@ -1,5 +1,6 @@
#import "RNFirebaseAuth.h" #import "RNFirebaseAuth.h"
#import "RNFirebaseEvents.h" #import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import "RCTDefines.h" #import "RCTDefines.h"
@ -28,9 +29,9 @@ RCT_EXPORT_METHOD(addAuthStateListener:
FIRApp *firApp = [FIRApp appNamed:appName]; FIRApp *firApp = [FIRApp appNamed:appName];
FIRAuthStateDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) { FIRAuthStateDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
if (user != nil) { 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 { } 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]; FIRApp *firApp = [FIRApp appNamed:appName];
FIRIDTokenDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addIDTokenDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) { FIRIDTokenDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addIDTokenDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) {
if (user != nil) { 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 { } 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; FIRUser *user = [FIRAuth authWithApp:firApp].currentUser;
if (user) { if (user) {
[user reloadWithCompletion:^(NSError *_Nullable error) { [self reloadAndReturnUser:user resolver:resolve rejecter: reject];
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
FIRUser *userAfterReload = [FIRAuth authWithApp:firApp].currentUser;
[self promiseWithUser:resolve rejecter:reject user:userAfterReload];
}
}];
} else { } else {
[self promiseNoUser:resolve rejecter:reject isError:YES]; [self promiseNoUser:resolve rejecter:reject isError:YES];
} }
@ -315,8 +309,7 @@ RCT_EXPORT_METHOD(updateEmail:
if (error) { if (error) {
[self promiseRejectAuthException:reject error:error]; [self promiseRejectAuthException:reject error:error];
} else { } else {
FIRUser *userAfterUpdate = [FIRAuth authWithApp:firApp].currentUser; [self reloadAndReturnUser:user resolver:resolve rejecter: reject];
[self promiseWithUser:resolve rejecter:reject user:userAfterUpdate];
} }
}]; }];
} else { } else {
@ -399,8 +392,7 @@ RCT_EXPORT_METHOD(updateProfile:
if (error) { if (error) {
[self promiseRejectAuthException:reject error:error]; [self promiseRejectAuthException:reject error:error];
} else { } else {
FIRUser *userAfterUpdate = [FIRAuth authWithApp:firApp].currentUser; [self reloadAndReturnUser:user resolver:resolve rejecter: reject];
[self promiseWithUser:resolve rejecter:reject user:userAfterUpdate];
} }
}]; }];
} else { } 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) { [[FIRPhoneAuthProvider providerWithAuth:[FIRAuth authWithApp:firApp]] verifyPhoneNumber:phoneNumber UIDelegate:nil completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
if (error) { if (error) {
NSDictionary * jsError = [self getJSError:(error)]; NSDictionary * jsError = [self getJSError:(error)];
NSMutableDictionary * props = [@{ NSDictionary *body = @{
@"type": @"onVerificationFailed", @"type": @"onVerificationFailed",
@"requestKey":requestKey, @"requestKey":requestKey,
@"state": @{@"error": jsError}, @"state": @{@"error": jsError},
} mutableCopy]; };
[self sendJSEventWithAppName:appName title:PHONE_AUTH_STATE_CHANGED_EVENT props: props]; [RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:PHONE_AUTH_STATE_CHANGED_EVENT body:body];
} else { } else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:verificationID forKey:@"authVerificationID"]; [defaults setObject:verificationID forKey:@"authVerificationID"];
NSMutableDictionary * props = [@{ NSDictionary *body = @{
@"type": @"onCodeSent", @"type": @"onCodeSent",
@"requestKey":requestKey, @"requestKey":requestKey,
@"state": @{@"verificationId": verificationID}, @"state": @{@"verificationId": verificationID},
} mutableCopy]; };
[self sendJSEventWithAppName:appName title:PHONE_AUTH_STATE_CHANGED_EVENT props: props]; [RNFirebaseUtil sendJSEventWithAppName:self appName:appName name:PHONE_AUTH_STATE_CHANGED_EVENT body:body];
} }
}]; }];
} }
@ -794,15 +786,7 @@ RCT_EXPORT_METHOD(unlink:
if (error) { if (error) {
[self promiseRejectAuthException:reject error:error]; [self promiseRejectAuthException:reject error:error];
} else { } else {
// This is here to protect against bugs in the iOS SDK which don't [self reloadAndReturnUser:user resolver:resolve rejecter: reject];
// 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];
}
}];
} }
}]; }];
} else { } else {
@ -916,6 +900,19 @@ RCT_EXPORT_METHOD(fetchProvidersForEmail:
return credential; 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 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 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 <Firebase.h>
#import "RNFirebaseDatabaseReference.h" #import "RNFirebaseDatabaseReference.h"
#import "RNFirebaseEvents.h" #import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
@implementation RNFirebaseDatabase @implementation RNFirebaseDatabase
RCT_EXPORT_MODULE(); RCT_EXPORT_MODULE();
@ -39,7 +40,7 @@ RCT_EXPORT_METHOD(keepSynced:(NSString *) appName
path:(NSString *) path path:(NSString *) path
modifiers:(NSArray *) modifiers modifiers:(NSArray *) modifiers
state:(BOOL) state) { 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]; [query keepSynced:state];
} }
@ -87,11 +88,7 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
dispatch_barrier_async(_transactionQueue, ^{ dispatch_barrier_async(_transactionQueue, ^{
[_transactions setValue:transactionState forKey:transactionId]; [_transactions setValue:transactionState forKey:transactionId];
NSDictionary *updateMap = [self createTransactionUpdateMap:appName transactionId:transactionId updatesData:currentData]; NSDictionary *updateMap = [self createTransactionUpdateMap:appName transactionId:transactionId updatesData:currentData];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233 [RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:updateMap];
// until a better solution comes around
if (self.bridge) {
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:updateMap];
}
}); });
// wait for the js event handler to call tryCommitTransaction // wait for the js event handler to call tryCommitTransaction
@ -118,11 +115,7 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
andCompletionBlock: andCompletionBlock:
^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) { ^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
NSDictionary *resultMap = [self createTransactionResultMap:appName transactionId:transactionId error:databaseError committed:committed snapshot: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 [RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:resultMap];
// until a better solution comes around
if (self.bridge) {
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:resultMap];
}
} }
withLocalEvents: withLocalEvents:
applyLocally]; applyLocally];
@ -233,13 +226,13 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
eventName:(NSString *) eventName eventName:(NSString *) eventName
resolver:(RCTPromiseResolveBlock) resolve resolver:(RCTPromiseResolveBlock) resolve
rejecter:(RCTPromiseRejectBlock) reject) { 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]; [ref once:eventName resolver:resolve rejecter:reject];
} }
RCT_EXPORT_METHOD(on:(NSString *) appName RCT_EXPORT_METHOD(on:(NSString *) appName
props:(NSDictionary *) props) { 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"]]; [ref on:props[@"eventType"] registration:props[@"registration"]];
} }
@ -278,16 +271,21 @@ RCT_EXPORT_METHOD(off:(NSString *) key
return [[RNFirebaseDatabase getDatabaseForApp:appName] referenceWithPath:path]; 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]; RNFirebaseDatabaseReference *ref = _dbReferences[key];
if (ref == nil) { if (ref == nil) {
ref = [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self app:appName key:key refPath:path modifiers:modifiers]; ref = [[RNFirebaseDatabaseReference alloc] initWithPathAndModifiers:self app:appName key:key refPath:path modifiers:modifiers];
if (keep) {
_dbReferences[key] = ref; _dbReferences[key] = ref;
} }
}
return ref; return ref;
} }

View File

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

View File

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

View File

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

View File

@ -81,20 +81,22 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
} }
- (FIRQuery *)buildQuery { - (FIRQuery *)buildQuery {
FIRQuery *query = (FIRQuery*)[[RNFirebaseFirestore getFirestoreForApp:_app] collectionWithPath:_path]; FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:_app];
query = [self applyFilters:query]; FIRQuery *query = (FIRQuery*)[firestore collectionWithPath:_path];
query = [self applyFilters:firestore query:query];
query = [self applyOrders:query]; query = [self applyOrders:query];
query = [self applyOptions:query]; query = [self applyOptions:firestore query:query];
return query; return query;
} }
- (FIRQuery *)applyFilters:(FIRQuery *) query { - (FIRQuery *)applyFilters:(FIRFirestore *) firestore
query:(FIRQuery *) query {
for (NSDictionary *filter in _filters) { for (NSDictionary *filter in _filters) {
NSString *fieldPath = filter[@"fieldPath"]; NSString *fieldPath = filter[@"fieldPath"];
NSString *operator = filter[@"operator"]; NSString *operator = filter[@"operator"];
// TODO: Validate this works NSDictionary *jsValue = filter[@"value"];
id value = filter[@"value"]; id value = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:jsValue];
if ([operator isEqualToString:@"EQUAL"]) { if ([operator isEqualToString:@"EQUAL"]) {
query = [query queryWhereField:fieldPath isEqualTo:value]; query = [query queryWhereField:fieldPath isEqualTo:value];
@ -121,12 +123,16 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
return query; return query;
} }
- (FIRQuery *)applyOptions:(FIRQuery *) query { - (FIRQuery *)applyOptions:(FIRFirestore *) firestore
query:(FIRQuery *) query {
if (_options[@"endAt"]) { if (_options[@"endAt"]) {
query = [query queryEndingAtValues:_options[@"endAt"]]; query = [query queryEndingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endAt"]]];
} }
if (_options[@"endBefore"]) { if (_options[@"endBefore"]) {
query = [query queryEndingBeforeValues:_options[@"endBefore"]]; query = [query queryEndingBeforeValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"endBefore"]]];
}
if (_options[@"limit"]) {
query = [query queryLimitedTo:[_options[@"limit"] intValue]];
} }
if (_options[@"offset"]) { if (_options[@"offset"]) {
// iOS doesn't support offset // iOS doesn't support offset
@ -135,10 +141,10 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
// iOS doesn't support selectFields // iOS doesn't support selectFields
} }
if (_options[@"startAfter"]) { if (_options[@"startAfter"]) {
query = [query queryStartingAfterValues:_options[@"startAfter"]]; query = [query queryStartingAfterValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAfter"]]];
} }
if (_options[@"startAt"]) { if (_options[@"startAt"]) {
query = [query queryStartingAtValues:_options[@"startAt"]]; query = [query queryStartingAtValues:[RNFirebaseFirestoreDocumentReference parseJSArray:firestore jsArray:_options[@"startAt"]]];
} }
return query; return query;
} }
@ -151,11 +157,7 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
[event setValue:listenerId forKey:@"listenerId"]; [event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"]; [event setValue:[RNFirebaseFirestore getJSError:error] forKey:@"error"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233 [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
} }
- (void)handleQuerySnapshotEvent:(NSString *)listenerId - (void)handleQuerySnapshotEvent:(NSString *)listenerId
@ -166,11 +168,7 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
[event setValue:listenerId forKey:@"listenerId"]; [event setValue:listenerId forKey:@"listenerId"];
[event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"]; [event setValue:[RNFirebaseFirestoreCollectionReference snapshotToDictionary:querySnapshot] forKey:@"querySnapshot"];
// TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233 [RNFirebaseUtil sendJSEvent:self.emitter name:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
// until a better solution comes around
if (_emitter.bridge) {
[_emitter sendEventWithName:FIRESTORE_COLLECTION_SYNC_EVENT body:event];
}
} }
+ (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot { + (NSDictionary *)snapshotToDictionary:(FIRQuerySnapshot *)querySnapshot {

View File

@ -9,6 +9,7 @@
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
#import "RNFirebaseEvents.h" #import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h" #import "RNFirebaseFirestore.h"
#import "RNFirebaseUtil.h"
@interface RNFirebaseFirestoreDocumentReference : NSObject @interface RNFirebaseFirestoreDocumentReference : NSObject
@property RCTEventEmitter *emitter; @property RCTEventEmitter *emitter;
@ -25,7 +26,10 @@
- (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject; - (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (BOOL)hasListeners; - (BOOL)hasListeners;
+ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot; + (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot;
+(NSDictionary *)parseJSMap:(FIRFirestore *) firestore jsMap:(NSDictionary *) jsMap; + (NSDictionary *)parseJSMap:(FIRFirestore *) firestore jsMap:(NSDictionary *) jsMap;
+ (NSArray *)parseJSArray:(FIRFirestore *) firestore jsArray:(NSArray *) jsArray;
+ (id)parseJSTypeMap:(FIRFirestore *) firestore jsTypeMap:(NSDictionary *) jsTypeMap;
@end @end
#else #else

View File

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

View File

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

View File

@ -3,6 +3,7 @@
#if __has_include(<FirebaseStorage/FIRStorage.h>) #if __has_include(<FirebaseStorage/FIRStorage.h>)
#import "RNFirebaseEvents.h" #import "RNFirebaseEvents.h"
#import "RNFirebaseUtil.h"
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
#import <Photos/Photos.h> #import <Photos/Photos.h>
#import <Firebase.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 { - (void)sendJSEvent:(NSString *)appName type:(NSString *)type path:(NSString *)path title:(NSString *)title props:(NSDictionary *)props {
@try { [RNFirebaseUtil sendJSEvent:self name:type body:@{@"eventName": title, @"appName": appName, @"path": path, @"body": props}];
[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);
}
} }
/** /**

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ export default class Query {
*/ */
orderBy(name: string, key?: string) { orderBy(name: string, key?: string) {
this.modifiers.push({ this.modifiers.push({
id: `orderBy-${name}:${key}`,
type: 'orderBy', type: 'orderBy',
name, name,
key, key,
@ -42,6 +43,7 @@ export default class Query {
*/ */
limit(name: string, limit: number) { limit(name: string, limit: number) {
this.modifiers.push({ this.modifiers.push({
id: `limit-${name}:${limit}`,
type: 'limit', type: 'limit',
name, name,
limit, limit,
@ -59,6 +61,7 @@ export default class Query {
*/ */
filter(name: string, value: any, key?: string) { filter(name: string, value: any, key?: string) {
this.modifiers.push({ this.modifiers.push({
id: `filter-${name}:${objectToUniqueId(value)}:${key}`,
type: 'filter', type: 'filter',
name, name,
value, value,
@ -82,14 +85,21 @@ export default class Query {
* @return {*} * @return {*}
*/ */
queryIdentifier() { queryIdentifier() {
// convert query modifiers array into an object for generating a unique key // sort modifiers to enforce ordering
const object = {}; 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++) { // Convert modifiers to unique key
const { name, type, value } = this.modifiers[i]; let key = '{';
object[`${type}-${name}`] = value; 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 the callback.
// Remove only a single registration // Remove only a single registration
if (eventType && originalCallback) { 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 // 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 // remove only the first registration to match firebase web sdk
// call multiple times to remove multiple registrations // call multiple times to remove multiple registrations
return this._syncTree.removeListenerRegistrations(originalCallback, [registrations[0]]); return this._syncTree.removeListenerRegistrations(originalCallback, [registration]);
} }
// Firebase Docs: // Firebase Docs:

View File

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

View File

@ -83,10 +83,6 @@ export default class CollectionReference {
return this._query.orderBy(fieldPath, directionStr); return this._query.orderBy(fieldPath, directionStr);
} }
select(varArgs: string[]): Query {
return this._query.select(varArgs);
}
startAfter(fieldValues: any): Query { startAfter(fieldValues: any): Query {
return this._query.startAfter(fieldValues); return this._query.startAfter(fieldValues);
} }

View File

@ -5,6 +5,7 @@
import DocumentSnapshot from './DocumentSnapshot'; import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path'; import Path from './Path';
import QuerySnapshot from './QuerySnapshot'; import QuerySnapshot from './QuerySnapshot';
import { buildNativeArray, buildTypeMap } from './utils/serialize';
import { firestoreAutoId, isFunction, isObject } from '../../utils'; import { firestoreAutoId, isFunction, isObject } from '../../utils';
const DIRECTIONS = { const DIRECTIONS = {
@ -77,24 +78,20 @@ export default class Query {
return this._firestore; return this._firestore;
} }
endAt(fieldValues: any): Query { endAt(...snapshotOrVarArgs: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = { const options = {
...this._queryOptions, ...this._queryOptions,
endAt: fieldValues, endAt: this._buildOrderByOption(snapshotOrVarArgs),
}; };
return new Query(this.firestore, this._referencePath, this._fieldFilters, return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options); this._fieldOrders, options);
} }
endBefore(fieldValues: any): Query { endBefore(...snapshotOrVarArgs: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = { const options = {
...this._queryOptions, ...this._queryOptions,
endBefore: fieldValues, endBefore: this._buildOrderByOption(snapshotOrVarArgs),
}; };
return new Query(this.firestore, this._referencePath, this._fieldFilters, return new Query(this.firestore, this._referencePath, this._fieldFilters,
@ -233,24 +230,20 @@ export default class Query {
combinedOrders, this._queryOptions); combinedOrders, this._queryOptions);
} }
startAfter(fieldValues: any): Query { startAfter(...snapshotOrVarArgs: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = { const options = {
...this._queryOptions, ...this._queryOptions,
startAfter: fieldValues, startAfter: this._buildOrderByOption(snapshotOrVarArgs),
}; };
return new Query(this.firestore, this._referencePath, this._fieldFilters, return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options); this._fieldOrders, options);
} }
startAt(fieldValues: any): Query { startAt(...snapshotOrVarArgs: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
const options = { const options = {
...this._queryOptions, ...this._queryOptions,
startAt: fieldValues, startAt: this._buildOrderByOption(snapshotOrVarArgs),
}; };
return new Query(this.firestore, this._referencePath, this._fieldFilters, return new Query(this.firestore, this._referencePath, this._fieldFilters,
@ -261,10 +254,11 @@ export default class Query {
// TODO: Validation // TODO: Validation
// validate.isFieldPath('fieldPath', fieldPath); // validate.isFieldPath('fieldPath', fieldPath);
// validate.isFieldFilter('fieldFilter', opStr, value); // validate.isFieldFilter('fieldFilter', opStr, value);
const nativeValue = buildTypeMap(value);
const newFilter = { const newFilter = {
fieldPath, fieldPath,
operator: OPERATORS[opStr], operator: OPERATORS[opStr],
value, value: nativeValue,
}; };
const combinedFilters = this._fieldFilters.concat(newFilter); const combinedFilters = this._fieldFilters.concat(newFilter);
return new Query(this.firestore, this._referencePath, combinedFilters, return new Query(this.firestore, this._referencePath, combinedFilters,
@ -275,6 +269,23 @@ export default class Query {
* INTERNALS * INTERNALS
*/ */
_buildOrderByOption(snapshotOrVarArgs: any[]) {
// TODO: Validation
let values;
if (snapshotOrVarArgs.length === 1 && snapshotOrVarArgs[0] instanceof DocumentSnapshot) {
const docSnapshot = snapshotOrVarArgs[0];
values = [];
for (let i = 0; i < this._fieldOrders.length; i++) {
const fieldOrder = this._fieldOrders[i];
values.push(docSnapshot.get(fieldOrder.fieldPath));
}
} else {
values = snapshotOrVarArgs;
}
return buildNativeArray(values);
}
/** /**
* Remove query snapshot listener * Remove query snapshot listener
* @param listener * @param listener

View File

@ -27,7 +27,7 @@ export const buildNativeMap = (data: Object): Object => {
return nativeData; return nativeData;
}; };
const buildNativeArray = (array: Object[]): any[] => { export const buildNativeArray = (array: Object[]): any[] => {
const nativeArray = []; const nativeArray = [];
if (array) { if (array) {
array.forEach((value) => { array.forEach((value) => {
@ -37,7 +37,7 @@ const buildNativeArray = (array: Object[]): any[] => {
return nativeArray; return nativeArray;
}; };
const buildTypeMap = (value: any): any => { export const buildTypeMap = (value: any): any => {
const typeMap = {}; const typeMap = {};
const type = typeOf(value); const type = typeOf(value);
if (value === null || value === undefined) { if (value === null || value === undefined) {
@ -67,7 +67,7 @@ const buildTypeMap = (value: any): any => {
}; };
} else if (value instanceof Date) { } else if (value instanceof Date) {
typeMap.type = 'date'; typeMap.type = 'date';
typeMap.value = value.toISOString(); typeMap.value = value.getTime();
} else { } else {
typeMap.type = 'object'; typeMap.type = 'object';
typeMap.value = buildNativeMap(value); typeMap.value = buildNativeMap(value);

View File

@ -11,6 +11,7 @@ type Registration = {
once?: Boolean, once?: Boolean,
appName: String, appName: String,
eventType: String, eventType: String,
listener: Function,
eventRegistrationKey: String, eventRegistrationKey: String,
ref: DatabaseReference, ref: DatabaseReference,
} }
@ -197,6 +198,28 @@ export default class SyncTree {
return Object.keys(this._tree[path][eventType]); 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. * Register a new listener.
@ -211,8 +234,8 @@ export default class SyncTree {
if (!this._tree[path]) this._tree[path] = {}; if (!this._tree[path]) this._tree[path] = {};
if (!this._tree[path][eventType]) this._tree[path][eventType] = {}; if (!this._tree[path][eventType]) this._tree[path][eventType] = {};
this._tree[path][eventType][eventRegistrationKey] = 0; this._tree[path][eventType][eventRegistrationKey] = listener;
this._reverseLookup[eventRegistrationKey] = Object.assign({}, parameters); this._reverseLookup[eventRegistrationKey] = Object.assign({ listener }, parameters);
if (once) { if (once) {
INTERNALS.SharedEventEmitter.once( INTERNALS.SharedEventEmitter.once(

2
package-lock.json generated
View File

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

View File

@ -16,7 +16,8 @@
"tests-npm-install": "cd tests && npm install", "tests-npm-install": "cd tests && npm install",
"tests-pod-install": "cd tests && npm run ios:pod:install", "tests-pod-install": "cd tests && npm run ios:pod:install",
"tests-watch-start": "npm run test-cli watch init start", "tests-watch-start": "npm run test-cli watch init start",
"tests-watch-stop": "npm run test-cli watch stop" "tests-watch-stop": "npm run test-cli watch stop",
"postinstall": "opencollective postinstall"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -88,6 +89,7 @@
}, },
"dependencies": { "dependencies": {
"bows": "^1.6.0", "bows": "^1.6.0",
"opencollective": "^1.0.3",
"prop-types": "^15.6.0" "prop-types": "^15.6.0"
}, },
"rnpm": { "rnpm": {
@ -99,5 +101,10 @@
"commands": { "commands": {
"postlink": "node node_modules/react-native-firebase/scripts/rnpm-postlink" "postlink": "node node_modules/react-native-firebase/scripts/rnpm-postlink"
} }
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/react-native-firebase",
"logo": "https://opencollective.com/opencollective/logo.txt"
} }
} }

View File

@ -150,7 +150,7 @@ PODS:
- React/Core - React/Core
- React/fishhook - React/fishhook
- React/RCTBlob - React/RCTBlob
- RNFirebase (3.1.0-alpha.1): - RNFirebase (3.0.5):
- React - React
- yoga (0.49.1.React) - yoga (0.49.1.React)
@ -176,11 +176,11 @@ DEPENDENCIES:
EXTERNAL SOURCES: EXTERNAL SOURCES:
React: React:
:path: ../node_modules/react-native :path: "../node_modules/react-native"
RNFirebase: RNFirebase:
:path: ./../../ :path: "./../../"
yoga: yoga:
:path: ../node_modules/react-native/ReactCommon/yoga :path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS: SPEC CHECKSUMS:
BoringSSL: 19083b821ef3ae0f758fae15482e183003b1e265 BoringSSL: 19083b821ef3ae0f758fae15482e183003b1e265
@ -199,7 +199,7 @@ SPEC CHECKSUMS:
FirebaseStorage: 0cca42d9b889a0227c3a50121f45a4469fc9eb27 FirebaseStorage: 0cca42d9b889a0227c3a50121f45a4469fc9eb27
Google-Mobile-Ads-SDK: ed8004a7265b424568dc84f3d2bbe3ea3fff958f Google-Mobile-Ads-SDK: ed8004a7265b424568dc84f3d2bbe3ea3fff958f
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0 GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
gRPC: 07788969b862af21491908f82b83d17ac08c94cd gRPC: '07788969b862af21491908f82b83d17ac08c94cd'
gRPC-Core: f707ade59c559fe718e27713189607d03b15f571 gRPC-Core: f707ade59c559fe718e27713189607d03b15f571
gRPC-ProtoRPC: de7505e493a9d1b6b96c8ea8f976c73100fdf53f gRPC-ProtoRPC: de7505e493a9d1b6b96c8ea8f976c73100fdf53f
gRPC-RxLibrary: 17b9699beb0a838b95b57832244f9ead18e66777 gRPC-RxLibrary: 17b9699beb0a838b95b57832244f9ead18e66777
@ -208,9 +208,9 @@ SPEC CHECKSUMS:
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8 Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8
React: cf892fb84b7d06bf5fea7f328e554c6dcabe85ee React: cf892fb84b7d06bf5fea7f328e554c6dcabe85ee
RNFirebase: 0467ca8122b9257acd7f1bb6de1670d9fd51cede RNFirebase: 7c86b4efd2860700048d927f34db237fbce1d5fc
yoga: 3abf02d6d9aeeb139b4c930eb1367feae690a35a yoga: 3abf02d6d9aeeb139b4c930eb1367feae690a35a
PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45 PODFILE CHECKSUM: b5674be55653f5dda937c8b794d0479900643d45
COCOAPODS: 1.3.1 COCOAPODS: 1.2.1

View File

@ -1,159 +0,0 @@
import React, { Component } from 'react';
import { View, SectionList, Text, Button } from 'react-native';
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
bgColor: '#cb2600',
};
}
clickMe = () => {
if (this.state.bgColor === '#a8139f') {
this.setState({ bgColor: '#cb2600' });
} else {
this.setState({ bgColor: '#a8139f' });
}
};
render() {
return (
<View style={{ backgroundColor: this.state.bgColor }}>
<Text style={{ color: '#fff' }}>Hello</Text>
<Text style={{ color: '#22ff31' }}>World</Text>
<Button title="Change to pink" onPress={this.clickMe} />
</View>
);
}
}
const sampleData = {
somePostId1: {
title: 'today now',
timestamp: Date.now(),
startOfDay: 1502838000,
},
somePostId3: {
title: 'today but older',
timestamp: Date.now() - 10000000,
startOfDay: 1502838000,
},
somePostId4: {
title: 'today but even older',
timestamp: Date.now() - 60000000,
startOfDay: 1502838000,
},
somePostId2: {
title: 'hello yesterday',
timestamp: Date.now() - 82000000, // minus 23 hours - just to make it yesterday ;p
startOfDay: 1502751600, // yesterday ;p
},
somePostId5: {
title: 'hello yesterday but older',
timestamp: Date.now() - 82800000, // minus 23 hours - just to make it yesterday ;p
startOfDay: 1502751600, // yesterday ;p
},
};
// export default class PostsScreen extends Component {
// constructor(props) {
// super(props);
// this.ref = null;
// this.state = {
// postSections: [],
// };
// }
//
// componentDidMount() {
// // this.ref = firebase.database().ref('posts');
// // this.ref.on('value', this._onPostsUpdate);
// // just fake it to test
// this._onPostsUpdate({
// val() {
// return sampleData;
// },
// });
// }
//
// componentWillUnmount() {
// // always unsubscribe from realtime events when component unmounts
// // if (this.ref) {
// // this.ref.off('value', this._onPostsUpdate);
// // }
// }
//
// _onPostsUpdate(snapshot) {
// const value = snapshot.val() || {};
// const keys = Object.keys(value);
// const sections = {};
//
// // we'll group them now by date
// for (let i = 0, len = keys.length; i < len; i++) {
// const key = keys[i];
// const post = value[key];
//
// // assuming post will have a 'timestamp' field and a `startOfDay` field
// // start of day can be calculated as above `startOfToday`
//
// if (!sections[post.startOfDay]) {
// sections[post.startOfDay] = {
// title: 'Header - I will leave this up to you', // todo today/yesterday/3 days ago etc
// // will use this later to sort the sections so today is on top
// key: post.startOfDay,
// data: [],
// };
// }
//
// const data = Object.assign({ key }, post);
// // add a post to a specific section date
// // we'll push/unshift depending on the date, so they' appear in order
// if (!sections[post.startOfDay].data.length) {
// // array is empty so nothing to compare sort, just push it
// sections[post.startOfDay].data.push(data);
// } else {
// const previousTimestamp = sections[post.startOfDay].data[sections[post.startOfDay].data.length - 1].timestamp;
// if (previousTimestamp < data.timestamp) sections[post.startOfDay].data.unshift(data);
// else sections[post.startOfDay].data.push(data);
// }
// }
//
// this.setState({
// postSections: Object.values(sections).sort((a, b) => a.key > b.key).reverse(),
// });
// }
//
// _renderSectionItem = ({ item }) => {
// // todo your custom section item component
// // return (
// // <EventCell
// // userName={item.userName}
// // postTitle={item.postTitle}
// // />
// // );
//
// return <Text>{`${item.title} - ${item.timestamp}`}</Text>;
// };
//
// _renderSectionHeader = ({ section }) => {
// // todo your custom section header
// return (
// <Text style={{ backgroundColor: '#000', color: '#fff' }}>{section.title}</Text>
// );
// };
//
// render() {
// return (
// <SectionList
// sections={this.state.postSections}
// renderItem={this._renderSectionItem}
// renderSectionHeader={this._renderSectionHeader}
// />
// );
// }
// }

View File

@ -1,4 +1,6 @@
import should from 'should'; import should from 'should';
import sinon from 'sinon';
import 'should-sinon';
import DatabaseContents from '../../support/DatabaseContents'; import DatabaseContents from '../../support/DatabaseContents';
function issueTests({ describe, it, context, firebase }) { 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; 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', () => { xcontext('when a context', () => {
/** /**
* @todo Add tests for when a context is passed. Not sure what the intended * @todo Add tests for when a context is passed. Not sure what the intended

View File

@ -2,9 +2,9 @@ import sinon from 'sinon';
import 'should-sinon'; import 'should-sinon';
import should from 'should'; import should from 'should';
import { COL_1 } from './index'; import { COL_1, cleanCollection } from './index';
function collectionReferenceTests({ describe, it, context, firebase }) { function collectionReferenceTests({ describe, it, context, firebase, before, after }) {
describe('CollectionReference', () => { describe('CollectionReference', () => {
context('class', () => { context('class', () => {
it('should return instance methods', () => { it('should return instance methods', () => {
@ -445,6 +445,26 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
}); });
}); });
it('correctly handles == date values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('timestamp', '==', COL_1.timestamp)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
});
});
it('correctly handles == geopoint values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('geopoint', '==', COL_1.geopoint)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
});
});
it('correctly handles >= number values', () => { it('correctly handles >= number values', () => {
return firebase.native.firestore() return firebase.native.firestore()
.collection('collection-tests') .collection('collection-tests')
@ -458,6 +478,16 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
}); });
}); });
it('correctly handles >= geopoint values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('geopoint', '>=', new firebase.native.firestore.GeoPoint(-1, -1))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
});
});
it('correctly handles <= float values', () => { it('correctly handles <= float values', () => {
return firebase.native.firestore() return firebase.native.firestore()
.collection('collection-tests') .collection('collection-tests')
@ -470,6 +500,246 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
}); });
}); });
}); });
it('correctly handles limit', async () => {
const collectionTests = firebase.native.firestore().collection('collection-tests2');
await Promise.all([
collectionTests.doc('col1').set(COL_1),
collectionTests.doc('col2').set({ ...COL_1, daz: 234 }),
collectionTests.doc('col3').set({ ...COL_1, daz: 234 }),
collectionTests.doc('col4').set({ ...COL_1, daz: 234 }),
collectionTests.doc('col5').set({ ...COL_1, daz: 234 }),
]);
return collectionTests.limit(3)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
return cleanCollection(collectionTests);
});
});
});
context('cursors', () => {
let collectionTests;
before(async () => {
collectionTests = firebase.native.firestore().collection('collection-tests2');
await Promise.all([
collectionTests.doc('col1').set({ ...COL_1, foo: 'bar0' }),
collectionTests.doc('col2').set({ ...COL_1, foo: 'bar1', daz: 234, timestamp: new Date(2017, 2, 11, 10, 0, 0) }),
collectionTests.doc('col3').set({ ...COL_1, foo: 'bar2', daz: 345, timestamp: new Date(2017, 2, 12, 10, 0, 0) }),
collectionTests.doc('col4').set({ ...COL_1, foo: 'bar3', daz: 456, timestamp: new Date(2017, 2, 13, 10, 0, 0) }),
collectionTests.doc('col5').set({ ...COL_1, foo: 'bar4', daz: 567, timestamp: new Date(2017, 2, 14, 10, 0, 0) }),
]);
});
context('endAt', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').endAt(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').endAt(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').endAt('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
it('handles snapshots', async () => {
const collectionSnapshot = await collectionTests.orderBy('foo').get();
return collectionTests.orderBy('foo').endAt(collectionSnapshot.docs[2])
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
});
context('endBefore', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').endBefore(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').endBefore(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').endBefore('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
it('handles snapshots', async () => {
const collectionSnapshot = await collectionTests.orderBy('foo').get();
return collectionTests.orderBy('foo').endBefore(collectionSnapshot.docs[2])
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
});
context('startAt', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').startAt(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').startAt(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').startAt('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
it('handles snapshots', async () => {
const collectionSnapshot = await collectionTests.orderBy('foo').get();
return collectionTests.orderBy('foo').startAt(collectionSnapshot.docs[2])
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
});
context('startAfter', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').startAfter(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').startAfter(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').startAfter('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
it('handles snapshot', async () => {
const collectionSnapshot = await collectionTests.orderBy('foo').get();
return collectionTests.orderBy('foo').startAfter(collectionSnapshot.docs[2])
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
});
after(() => {
return cleanCollection(collectionTests);
});
}); });
}); });
} }

View File

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

View File

@ -14,7 +14,9 @@ export const COL_1 = {
daz: 123, daz: 123,
foo: 'bar', foo: 'bar',
gaz: 12.1234567, gaz: 12.1234567,
geopoint: new firebase.native.firestore.GeoPoint(0, 0),
naz: null, naz: null,
timestamp: new Date(2017, 2, 10, 10, 0, 0),
}; };
export const DOC_1 = { name: 'doc1' }; export const DOC_1 = { name: 'doc1' };
@ -65,7 +67,7 @@ suite.addTests(firestoreTestSuite);
export default suite; export default suite;
/* HELPER FUNCTIONS */ /* HELPER FUNCTIONS */
async function cleanCollection(collection) { export async function cleanCollection(collection) {
const collectionTestsDocs = await collection.get(); const collectionTestsDocs = await collection.get();
const tasks = []; const tasks = [];
collectionTestsDocs.forEach(doc => tasks.push(doc.ref.delete())); collectionTestsDocs.forEach(doc => tasks.push(doc.ref.delete()));

View File

@ -91,5 +91,28 @@ export default {
uid: 'aNYxLexOb2WsXGOPiEAu47q5bxH3', 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',
},
},
}, },
}; };