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) --->
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>
[![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)
[![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)
[![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)
@ -53,40 +52,70 @@ All in all, RNFirebase provides much faster performance (~2x) over the web SDK a
> '**?**' indicates partial support
| Firebase Features | v1.x.x | v2.x.x | v3.x.x | Web SDK |
| ---------------------- | :---: | :---: | :---: | :---: |
| **AdMob** | ❌ | ✅ | ✅ | ❌ |
| **Analytics**             | ✅ | ✅ | ✅ | ❌ |
| **App Indexing**           | ❌ | ❌ | ❌ | ❌ |
| **Authentication** | ✅ | ✅ | ✅ | ✅ |
| _-- Phone Auth_ | ❌ | ❌ | ✅ | ❌ |
| **Core** | ❌ |**?**| ✅ | ✅ |
| _-- Multiple Apps_ | ❌ | ❌ | ✅ | ✅ |
| **Cloud Firestore** | ❌ | ❌ | ✅ | ❌ |
| **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ |**?**|
| **Crash Reporting** | ✅ | ✅ | ✅ | ❌ |
| **Dynamic Links** | ❌ | ❌ | ❌ | ❌ |
| **Invites** | ❌ | ❌ | ❌ | ❌ |
| **Performance Monitoring** | ✅ | ✅ | ✅ | ❌ |
| **Realtime Database** | ✅ | ✅ | ✅ | ✅ |
| _-- Offline Persistence_ | ✅ | ✅ | ✅ |**?**|
| _-- Transactions_ | ✅ | ✅ | ✅ | ✅ |
| **Remote Config** | ✅ | ✅ | ✅ | ❌ |
| **Storage** | ✅ | ✅ | ✅ |**?**|
| Firebase Features | v1.x.x | v2.x.x | v3.x.x | v3.1.x | Web SDK |
| ---------------------- | :---: | :---: | :---: | :---: | :---: |
| **AdMob** | ❌ | ✅ | ✅ | ✅ | ❌ |
| **Analytics**             | ✅ | ✅ | ✅ | ✅ | ❌ |
| **App Indexing**           | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Authentication** | ✅ | ✅ | ✅ | ✅ | ✅ |
| _-- Phone Auth_ | ❌ | ❌ | ✅ | ✅ | ❌ |
| **Core** | ❌ |**?**| ✅ | ✅ | ✅ |
| _-- Multiple Apps_ | ❌ | ❌ | ✅ | ✅ | ✅ |
| **Cloud Firestore** | ❌ | ❌ | ✅ | ✅ | ❌ |
| **Cloud Messaging (FCM)** | ✅ | ✅ | ✅ | ✅ |**?**|
| **Crash Reporting** | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Dynamic Links** | ❌ | ❌ | ❌ | ✅ | ❌ |
| **Invites** | ❌ | ❌ | ❌ |**?**| ❌ |
| **Performance Monitoring** | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Realtime Database** | ✅ | ✅ | ✅ | ✅ | ✅ |
| _-- Offline Persistence_ | ✅ | ✅ | ✅ | ✅ |**?**|
| _-- Transactions_ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Remote Config** | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Storage** | ✅ | ✅ | ✅ | ✅ |**?**|
---
### 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`
| | 1.X.X | 2.0.X | 2.1.X / 2.2.X | 3.0.X |
|------------------------|-------------|-------------|-----------------|----------|
| React Native | 0.36 - 0.39 | 0.40 - 0.46 | 0.47 + | 0.48 + |
| Firebase Android SDK | 10.2.0 + | 11.0.0 + | 11.0.0 + | 11.4.2 + |
| Firebase iOS SDK | 3.15.0 + | 4.0.0 + | 4.0.0 + | 4.3.0 + |
| | 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 + | 0.48 + |
| 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 + | 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
- 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.AuthResult;
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
import com.google.firebase.auth.FirebaseAuthProvider;
import com.google.firebase.auth.GithubAuthProvider;
import com.google.firebase.auth.PhoneAuthCredential;
import com.google.firebase.auth.PhoneAuthProvider;
@ -54,6 +55,7 @@ import io.invertase.firebase.Utils;
class RNFirebaseAuth extends ReactContextBaseJavaModule {
private static final String TAG = "RNFirebaseAuth";
private String mVerificationId;
private PhoneAuthCredential mCredential;
private ReactContext mReactContext;
private HashMap<String, FirebaseAuth.AuthStateListener> mAuthListeners = new HashMap<>();
private HashMap<String, FirebaseAuth.IdTokenListener> mIdTokenListeners = new HashMap<>();
@ -738,10 +740,16 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
Log.d(TAG, "verifyPhoneNumber:" + phoneNumber);
// Reset the credential
mCredential = null;
PhoneAuthProvider.OnVerificationStateChangedCallbacks callbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
@Override
public void onVerificationCompleted(final PhoneAuthCredential phoneAuthCredential) {
// Cache the credential to protect against null verificationId
mCredential = phoneAuthCredential;
Log.d(TAG, "verifyPhoneNumber:verification:onVerificationCompleted");
WritableMap state = Arguments.createMap();
@ -1068,6 +1076,15 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
case "github.com":
return GithubAuthProvider.getCredential(authToken);
case "phone":
// If the phone number is auto-verified quickly, then the verificationId can be null
// We cached the credential as part of the verifyPhoneNumber request to be re-used here
// if possible
if (authToken == null && mCredential != null) {
PhoneAuthCredential credential = mCredential;
// Reset the cached credential
mCredential = null;
return credential;
}
return PhoneAuthProvider.getCredential(authToken, authSecret);
case "password":
return EmailAuthProvider.getCredential(authToken, authSecret);
@ -1282,12 +1299,12 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
* @param providerData List<UserInfo> user.getProviderData()
* @return WritableArray array
*/
private WritableArray convertProviderData(List<? extends UserInfo> providerData) {
private WritableArray convertProviderData(List<? extends UserInfo> providerData, FirebaseUser user) {
WritableArray output = Arguments.createArray();
for (UserInfo userInfo : providerData) {
// remove 'firebase' provider data - android fb sdk
// should not be returning this as the ios/web ones don't
if (!userInfo.getProviderId().equals("firebase")) {
if (!FirebaseAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())) {
WritableMap userInfoMap = Arguments.createMap();
userInfoMap.putString("providerId", userInfo.getProviderId());
userInfoMap.putString("uid", userInfo.getUid());
@ -1295,20 +1312,34 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
final Uri photoUrl = userInfo.getPhotoUrl();
if (photoUrl != null) {
if (photoUrl != null && !"".equals(photoUrl)) {
userInfoMap.putString("photoURL", photoUrl.toString());
} else {
userInfoMap.putNull("photoURL");
}
final String phoneNumber = userInfo.getPhoneNumber();
if (phoneNumber != null) {
// The Android SDK is missing the phone number property for the phone provider when the
// user first signs up using their phone number. Use the phone number from the user
// object instead
if (PhoneAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())
&& (userInfo.getPhoneNumber() == null || "".equals(userInfo.getPhoneNumber()))) {
userInfoMap.putString("phoneNumber", user.getPhoneNumber());
} else if (phoneNumber != null && !"".equals(phoneNumber)) {
userInfoMap.putString("phoneNumber", phoneNumber);
} else {
userInfoMap.putNull("phoneNumber");
}
userInfoMap.putString("email", userInfo.getEmail());
// The Android SDK is missing the email property for the email provider, so we use UID instead
if (EmailAuthProvider.PROVIDER_ID.equals(userInfo.getProviderId())
&& (userInfo.getEmail() == null || "".equals(userInfo.getEmail()))) {
userInfoMap.putString("email", userInfo.getUid());
} else if (userInfo.getEmail() != null && !"".equals(userInfo.getEmail())) {
userInfoMap.putString("email", userInfo.getEmail());
} else {
userInfoMap.putNull("email");
}
output.pushMap(userInfoMap);
}
@ -1339,31 +1370,31 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
userMap.putBoolean("emailVerified", verified);
userMap.putBoolean("isAnonymous", user.isAnonymous());
if (email != null) {
if (email != null && !"".equals(email)) {
userMap.putString("email", email);
} else {
userMap.putNull("email");
}
if (name != null) {
if (name != null && !"".equals(name)) {
userMap.putString("displayName", name);
} else {
userMap.putNull("displayName");
}
if (photoUrl != null) {
if (photoUrl != null && !"".equals(photoUrl)) {
userMap.putString("photoURL", photoUrl.toString());
} else {
userMap.putNull("photoURL");
}
if (phoneNumber != null) {
if (phoneNumber != null && !"".equals(phoneNumber)) {
userMap.putString("phoneNumber", phoneNumber);
} else {
userMap.putNull("phoneNumber");
}
userMap.putArray("providerData", convertProviderData(user.getProviderData()));
userMap.putArray("providerData", convertProviderData(user.getProviderData(), user));
return userMap;
}

View File

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

View File

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

View File

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

View File

@ -12,8 +12,6 @@ import java.util.Map;
import java.util.HashMap;
import android.net.Uri;
import android.database.Cursor;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
@ -339,14 +337,7 @@ public class RNFirebaseStorage extends ReactContextBaseJavaModule {
Log.i(TAG, "putFile: " + localPath + " to " + path);
try {
Uri file;
if (localPath.startsWith("content://")) {
String realPath = getRealPathFromURI(localPath);
file = Uri.fromFile(new File(realPath));
} else {
file = Uri.fromFile(new File(localPath));
}
Uri file = getURI(localPath);
StorageMetadata md = buildMetadataFromMap(metadata);
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
* @return
*/
private String getRealPathFromURI(final String uri) {
Cursor cursor = null;
try {
String[] proj = {MediaStore.Images.Media.DATA};
cursor = getReactApplicationContext().getContentResolver().query(Uri.parse(uri), proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
private Uri getURI(final String uri) {
Uri parsed = Uri.parse(uri);
if (parsed.getScheme() == null || parsed.getScheme().isEmpty()) {
return Uri.fromFile(new File(uri));
}
return parsed;
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@
#import <React/RCTEventEmitter.h>
#import "RNFirebaseEvents.h"
#import "RNFirebaseFirestore.h"
#import "RNFirebaseUtil.h"
@interface RNFirebaseFirestoreDocumentReference : NSObject
@property RCTEventEmitter *emitter;
@ -25,7 +26,10 @@
- (void)update:(NSDictionary *)data resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject;
- (BOOL)hasListeners;
+ (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
#else

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path';
import QuerySnapshot from './QuerySnapshot';
import { buildNativeArray, buildTypeMap } from './utils/serialize';
import { firestoreAutoId, isFunction, isObject } from '../../utils';
const DIRECTIONS = {
@ -77,24 +78,20 @@ export default class Query {
return this._firestore;
}
endAt(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
endAt(...snapshotOrVarArgs: any): Query {
const options = {
...this._queryOptions,
endAt: fieldValues,
endAt: this._buildOrderByOption(snapshotOrVarArgs),
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
endBefore(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
endBefore(...snapshotOrVarArgs: any): Query {
const options = {
...this._queryOptions,
endBefore: fieldValues,
endBefore: this._buildOrderByOption(snapshotOrVarArgs),
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
@ -233,24 +230,20 @@ export default class Query {
combinedOrders, this._queryOptions);
}
startAfter(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
startAfter(...snapshotOrVarArgs: any): Query {
const options = {
...this._queryOptions,
startAfter: fieldValues,
startAfter: this._buildOrderByOption(snapshotOrVarArgs),
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
this._fieldOrders, options);
}
startAt(fieldValues: any): Query {
fieldValues = [].slice.call(arguments);
// TODO: Validation
startAt(...snapshotOrVarArgs: any): Query {
const options = {
...this._queryOptions,
startAt: fieldValues,
startAt: this._buildOrderByOption(snapshotOrVarArgs),
};
return new Query(this.firestore, this._referencePath, this._fieldFilters,
@ -261,10 +254,11 @@ export default class Query {
// TODO: Validation
// validate.isFieldPath('fieldPath', fieldPath);
// validate.isFieldFilter('fieldFilter', opStr, value);
const nativeValue = buildTypeMap(value);
const newFilter = {
fieldPath,
operator: OPERATORS[opStr],
value,
value: nativeValue,
};
const combinedFilters = this._fieldFilters.concat(newFilter);
return new Query(this.firestore, this._referencePath, combinedFilters,
@ -275,6 +269,23 @@ export default class Query {
* 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
* @param listener

View File

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

View File

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

2
package-lock.json generated
View File

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

View File

@ -16,7 +16,8 @@
"tests-npm-install": "cd tests && npm install",
"tests-pod-install": "cd tests && npm run ios:pod:install",
"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": {
"type": "git",
@ -88,6 +89,7 @@
},
"dependencies": {
"bows": "^1.6.0",
"opencollective": "^1.0.3",
"prop-types": "^15.6.0"
},
"rnpm": {
@ -99,5 +101,10 @@
"commands": {
"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/fishhook
- React/RCTBlob
- RNFirebase (3.1.0-alpha.1):
- RNFirebase (3.0.5):
- React
- yoga (0.49.1.React)
@ -176,11 +176,11 @@ DEPENDENCIES:
EXTERNAL SOURCES:
React:
:path: ../node_modules/react-native
:path: "../node_modules/react-native"
RNFirebase:
:path: ./../../
:path: "./../../"
yoga:
:path: ../node_modules/react-native/ReactCommon/yoga
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
BoringSSL: 19083b821ef3ae0f758fae15482e183003b1e265
@ -199,7 +199,7 @@ SPEC CHECKSUMS:
FirebaseStorage: 0cca42d9b889a0227c3a50121f45a4469fc9eb27
Google-Mobile-Ads-SDK: ed8004a7265b424568dc84f3d2bbe3ea3fff958f
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
gRPC: 07788969b862af21491908f82b83d17ac08c94cd
gRPC: '07788969b862af21491908f82b83d17ac08c94cd'
gRPC-Core: f707ade59c559fe718e27713189607d03b15f571
gRPC-ProtoRPC: de7505e493a9d1b6b96c8ea8f976c73100fdf53f
gRPC-RxLibrary: 17b9699beb0a838b95b57832244f9ead18e66777
@ -208,9 +208,9 @@ SPEC CHECKSUMS:
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Protobuf: 03eef2ee0b674770735cf79d9c4d3659cf6908e8
React: cf892fb84b7d06bf5fea7f328e554c6dcabe85ee
RNFirebase: 0467ca8122b9257acd7f1bb6de1670d9fd51cede
RNFirebase: 7c86b4efd2860700048d927f34db237fbce1d5fc
yoga: 3abf02d6d9aeeb139b4c930eb1367feae690a35a
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 sinon from 'sinon';
import 'should-sinon';
import DatabaseContents from '../../support/DatabaseContents';
function issueTests({ describe, it, context, firebase }) {
@ -81,6 +83,212 @@ function issueTests({ describe, it, context, firebase }) {
});
});
});
describe('issue_489', () => {
context('long numbers should', () => {
it('return as longs', async () => {
// Setup
const long1Ref = firebase.native.database().ref('tests/issues/489/long1');
const long2Ref = firebase.native.database().ref('tests/issues/489/long2');
const long2 = 1234567890123456;
// Test
let snapshot = await long1Ref.once('value');
snapshot.val().should.eql(DatabaseContents.ISSUES[489].long1);
await long2Ref.set(long2);
snapshot = await long2Ref.once('value');
snapshot.val().should.eql(long2);
return Promise.resolve();
});
});
});
describe('issue_521', () => {
context('orderByChild (numerical field) and limitToLast', () => {
it('once() returns correct results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521');
// Test
return ref
.orderByChild('number')
.limitToLast(1)
.once('value')
.then((snapshot) => {
const val = snapshot.val();
// Assertion
val.key3.should.eql(DatabaseContents.ISSUES[521].key3);
should.equal(Object.keys(val).length, 1);
return Promise.resolve();
});
});
it('on() returns correct initial results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('number').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
return Promise.resolve();
});
it('on() returns correct subsequent results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('number').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
const newDataValue = {
name: 'Item 4',
number: 4,
string: 'item4',
};
const newRef = firebase.native.database().ref('tests/issues/521/key4');
await newRef.set(newDataValue);
await new Promise((resolve) => {
setTimeout(() => resolve(), 5);
});
// Assertions
callback.should.be.calledWith({
key3: DatabaseContents.ISSUES[521].key3,
key4: newDataValue,
});
callback.should.be.calledTwice();
return Promise.resolve();
});
});
context('orderByChild (string field) and limitToLast', () => {
it('once() returns correct results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521');
// Test
return ref
.orderByChild('string')
.limitToLast(1)
.once('value')
.then((snapshot) => {
const val = snapshot.val();
// Assertion
val.key3.should.eql(DatabaseContents.ISSUES[521].key3);
should.equal(Object.keys(val).length, 1);
return Promise.resolve();
});
});
it('on() returns correct initial results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('string').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
return Promise.resolve();
});
it('on() returns correct subsequent results', async () => {
// Setup
const ref = firebase.native.database().ref('tests/issues/521').orderByChild('string').limitToLast(2);
const callback = sinon.spy();
// Test
await new Promise((resolve) => {
ref.on('value', (snapshot) => {
callback(snapshot.val());
resolve();
});
});
callback.should.be.calledWith({
key2: DatabaseContents.ISSUES[521].key2,
key3: DatabaseContents.ISSUES[521].key3,
});
callback.should.be.calledOnce();
const newDataValue = {
name: 'Item 4',
number: 4,
string: 'item4',
};
const newRef = firebase.native.database().ref('tests/issues/521/key4');
await newRef.set(newDataValue);
await new Promise((resolve) => {
setTimeout(() => resolve(), 5);
});
// Assertions
callback.should.be.calledWith({
key3: DatabaseContents.ISSUES[521].key3,
key4: newDataValue,
});
callback.should.be.calledTwice();
return Promise.resolve();
});
});
});
}
export default issueTests;

View File

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

View File

@ -2,9 +2,9 @@ import sinon from 'sinon';
import 'should-sinon';
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', () => {
context('class', () => {
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', () => {
return firebase.native.firestore()
.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', () => {
return firebase.native.firestore()
.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();
doc.data().field.should.be.instanceof(Date);
should.equal(doc.data().field.toISOString(), date.toISOString());
should.equal(doc.data().field.getTime(), date.getTime());
});
});

View File

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

View File

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