Merge branch 'master' into fcm-rewrite
# Conflicts: # lib/modules/core/firebase.js # tests/ios/Podfile.lock
This commit is contained in:
commit
7619548a2c
|
@ -55,7 +55,6 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(si
|
|||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowBug.*
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
coverage
|
||||
coverage.ios.json
|
||||
coverage.android.json
|
||||
node_modules
|
||||
npm-debug.log
|
||||
*.DS_Store
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
node_modules
|
||||
coverage.android.json
|
||||
coverage.ios.json
|
||||
coverage
|
||||
npm-debug.log
|
||||
*.DS_Store
|
||||
.github
|
||||
|
|
|
@ -7,10 +7,12 @@ First, thank you for considering contributing to react-native-firebase! It's peo
|
|||
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)
|
||||
* **Docs**: improve reference coverage, add more examples, fix any typos or anything else you can spot. At the top of every page on our docs site you can click the `Edit` pencil to go to that pages markdown file, or view the [Docs Repo](https://github.com/invertase/react-native-firebase-docs) directly
|
||||
* **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).
|
||||
* **Premium Starter Kits**: purchasing one of our starter kits helps us continue to develop the library and support its ever growing community (and you get something too!). [View Kits](http://invertase.link/get-started-premium)
|
||||
|
||||
## Your First Contribution
|
||||
|
||||
|
@ -32,8 +34,7 @@ Anyone can file an expense. If the expense makes sense for the development of th
|
|||
|
||||
## 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
|
||||
For questions and support please use our [Discord chat](https://discord.gg/C9aK28N) or [Stack Overflow](https://stackoverflow.com/questions/tagged/react-native-firebase). The issue list of this repo is **exclusively** for bug reports.
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2017 Invertase Limited <oss@invertase.io>
|
||||
Copyright (c) 2018 Invertase Limited <oss@invertase.io>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this library except in compliance with the License.
|
||||
|
|
|
@ -96,7 +96,7 @@ dependencies {
|
|||
compile "com.google.firebase:firebase-ads:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-firestore:$firebaseVersion"
|
||||
compile "com.google.firebase:firebase-invites:$firebaseVersion"
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.9.1@aar') {
|
||||
transitive = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1060,6 +1060,15 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
|
|||
|
||||
@ReactMethod
|
||||
public void reauthenticateWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) {
|
||||
reauthenticate(appName, provider, authToken, authSecret, promise, false);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void reauthenticateAndRetrieveDataWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) {
|
||||
reauthenticate(appName, provider, authToken, authSecret, promise, true);
|
||||
}
|
||||
|
||||
public void reauthenticate(String appName, String provider, String authToken, String authSecret, final Promise promise, final boolean withData) {
|
||||
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||
final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
|
||||
|
||||
|
@ -1072,13 +1081,17 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule {
|
|||
Log.d(TAG, "reauthenticate");
|
||||
|
||||
if (user != null) {
|
||||
user.reauthenticate(credential)
|
||||
.addOnCompleteListener(new OnCompleteListener<Void>() {
|
||||
user.reauthenticateAndRetrieveData(credential)
|
||||
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(@NonNull Task<Void> task) {
|
||||
public void onComplete(@NonNull Task<AuthResult> task) {
|
||||
if (task.isSuccessful()) {
|
||||
Log.d(TAG, "reauthenticate:onComplete:success");
|
||||
promiseWithUser(firebaseAuth.getCurrentUser(), promise);
|
||||
if (withData) {
|
||||
promiseWithAuthResult(task.getResult(), promise);
|
||||
} else {
|
||||
promiseWithUser(task.getResult().getUser(), promise);
|
||||
}
|
||||
} else {
|
||||
Exception exception = task.getException();
|
||||
Log.e(TAG, "reauthenticate:onComplete:failure", exception);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package io.invertase.firebase.firestore;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
@ -12,8 +14,11 @@ import com.facebook.react.bridge.ReadableArray;
|
|||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.firestore.Transaction;
|
||||
import com.google.firebase.firestore.DocumentReference;
|
||||
import com.google.firebase.firestore.FieldValue;
|
||||
import com.google.firebase.firestore.FirebaseFirestore;
|
||||
|
@ -26,11 +31,12 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import io.invertase.firebase.ErrorUtils;
|
||||
import io.invertase.firebase.Utils;
|
||||
|
||||
|
||||
public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseFirestore";
|
||||
// private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
private SparseArray<RNFirebaseFirestoreTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
|
||||
RNFirebaseFirestore(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
@ -92,7 +98,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
break;
|
||||
case "SET":
|
||||
Map<String, Object> options = (Map) write.get("options");
|
||||
if (options != null && options.containsKey("merge") && (boolean)options.get("merge")) {
|
||||
if (options != null && options.containsKey("merge") && (boolean) options.get("merge")) {
|
||||
batch = batch.set(ref, data, SetOptions.merge());
|
||||
} else {
|
||||
batch = batch.set(ref, data);
|
||||
|
@ -113,7 +119,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
promise.resolve(null);
|
||||
} else {
|
||||
Log.e(TAG, "documentBatch:onComplete:failure", task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException());
|
||||
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException) task.getException());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -160,6 +166,199 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
ref.update(data, promise);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try clean up previous transactions on reload
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
for (int i = 0, size = transactionHandlers.size(); i < size; i++) {
|
||||
RNFirebaseFirestoreTransactionHandler transactionHandler = transactionHandlers.get(i);
|
||||
if (transactionHandler != null) {
|
||||
transactionHandler.abort();
|
||||
}
|
||||
}
|
||||
|
||||
transactionHandlers.clear();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Transaction Methods
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Calls the internal Firestore Transaction classes instance .get(ref) method and resolves with
|
||||
* the DocumentSnapshot.
|
||||
*
|
||||
* @param appName
|
||||
* @param transactionId
|
||||
* @param path
|
||||
* @param promise
|
||||
*/
|
||||
@ReactMethod
|
||||
public void transactionGetDocument(String appName, int transactionId, String path, final Promise promise) {
|
||||
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||
|
||||
if (handler == null) {
|
||||
promise.reject(
|
||||
"internal-error",
|
||||
"An internal error occurred whilst attempting to find a native transaction by id."
|
||||
);
|
||||
} else {
|
||||
DocumentReference ref = getDocumentForAppPath(appName, path).getRef();
|
||||
handler.getDocument(ref, promise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts any pending signals and deletes the transaction handler.
|
||||
*
|
||||
* @param appName
|
||||
* @param transactionId
|
||||
*/
|
||||
@ReactMethod
|
||||
public void transactionDispose(String appName, int transactionId) {
|
||||
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||
|
||||
if (handler != null) {
|
||||
handler.abort();
|
||||
transactionHandlers.delete(transactionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to transactionHandler that the command buffer is ready.
|
||||
*
|
||||
* @param appName
|
||||
* @param transactionId
|
||||
* @param commandBuffer
|
||||
*/
|
||||
@ReactMethod
|
||||
public void transactionApplyBuffer(String appName, int transactionId, ReadableArray commandBuffer) {
|
||||
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||
|
||||
if (handler != null) {
|
||||
handler.signalBufferReceived(commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a new transaction via AsyncTask 's
|
||||
*
|
||||
* @param appName
|
||||
* @param transactionId
|
||||
*/
|
||||
@ReactMethod
|
||||
public void transactionBegin(final String appName, int transactionId) {
|
||||
final RNFirebaseFirestoreTransactionHandler transactionHandler = new RNFirebaseFirestoreTransactionHandler(appName, transactionId);
|
||||
transactionHandlers.put(transactionId, transactionHandler);
|
||||
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getFirestoreForApp(appName)
|
||||
.runTransaction(new Transaction.Function<Void>() {
|
||||
@Override
|
||||
public Void apply(@NonNull Transaction transaction) throws FirebaseFirestoreException {
|
||||
transactionHandler.resetState(transaction);
|
||||
|
||||
// emit the update cycle to JS land using an async task
|
||||
// otherwise it gets blocked by the pending lock await
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
WritableMap eventMap = transactionHandler.createEventMap(null, "update");
|
||||
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap);
|
||||
}
|
||||
});
|
||||
|
||||
// wait for a signal to be received from JS land code
|
||||
transactionHandler.await();
|
||||
|
||||
// exit early if aborted - has to throw an exception otherwise will just keep trying ...
|
||||
if (transactionHandler.aborted) {
|
||||
throw new FirebaseFirestoreException("abort", FirebaseFirestoreException.Code.ABORTED);
|
||||
}
|
||||
|
||||
// exit early if timeout from bridge - has to throw an exception otherwise will just keep trying ...
|
||||
if (transactionHandler.timeout) {
|
||||
throw new FirebaseFirestoreException("timeout", FirebaseFirestoreException.Code.DEADLINE_EXCEEDED);
|
||||
}
|
||||
|
||||
// process any buffered commands from JS land
|
||||
ReadableArray buffer = transactionHandler.getCommandBuffer();
|
||||
|
||||
// exit early if no commands
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0, size = buffer.size(); i < size; i++) {
|
||||
ReadableMap data;
|
||||
ReadableMap command = buffer.getMap(i);
|
||||
String path = command.getString("path");
|
||||
String type = command.getString("type");
|
||||
RNFirebaseFirestoreDocumentReference documentReference = getDocumentForAppPath(appName, path);
|
||||
|
||||
|
||||
switch (type) {
|
||||
case "set":
|
||||
data = command.getMap("data");
|
||||
|
||||
ReadableMap options = command.getMap("options");
|
||||
Map<String, Object> setData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||
|
||||
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
|
||||
transaction.set(documentReference.getRef(), setData, SetOptions.merge());
|
||||
} else {
|
||||
transaction.set(documentReference.getRef(), setData);
|
||||
}
|
||||
break;
|
||||
case "update":
|
||||
data = command.getMap("data");
|
||||
|
||||
Map<String, Object> updateData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
|
||||
transaction.update(documentReference.getRef(), updateData);
|
||||
break;
|
||||
case "delete":
|
||||
transaction.delete(documentReference.getRef());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown command type at index " + i + ".");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.addOnSuccessListener(new OnSuccessListener<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void aVoid) {
|
||||
if (!transactionHandler.aborted) {
|
||||
Log.d(TAG, "Transaction onSuccess!");
|
||||
WritableMap eventMap = transactionHandler.createEventMap(null, "complete");
|
||||
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap);
|
||||
}
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception e) {
|
||||
if (!transactionHandler.aborted) {
|
||||
Log.w(TAG, "Transaction onFailure.", e);
|
||||
WritableMap eventMap = transactionHandler.createEventMap((FirebaseFirestoreException) e, "error");
|
||||
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
@ -197,7 +396,7 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
|
|||
* @param filters
|
||||
* @param orders
|
||||
* @param options
|
||||
* @param path @return
|
||||
* @param path @return
|
||||
*/
|
||||
private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path,
|
||||
ReadableArray filters,
|
||||
|
|
|
@ -145,6 +145,10 @@ public class RNFirebaseFirestoreDocumentReference {
|
|||
* INTERNALS/UTILS
|
||||
*/
|
||||
|
||||
public DocumentReference getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
boolean hasListeners() {
|
||||
return !documentSnapshotListeners.isEmpty();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
package io.invertase.firebase.firestore;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.firebase.firestore.DocumentReference;
|
||||
import com.google.firebase.firestore.DocumentSnapshot;
|
||||
import com.google.firebase.firestore.FirebaseFirestoreException;
|
||||
import com.google.firebase.firestore.Transaction;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
class RNFirebaseFirestoreTransactionHandler {
|
||||
private String appName;
|
||||
private long timeoutAt;
|
||||
private int transactionId;
|
||||
private final ReentrantLock lock;
|
||||
private final Condition condition;
|
||||
private ReadableArray commandBuffer;
|
||||
private Transaction firestoreTransaction;
|
||||
|
||||
boolean aborted = false;
|
||||
boolean timeout = false;
|
||||
|
||||
RNFirebaseFirestoreTransactionHandler(String app, int id) {
|
||||
appName = app;
|
||||
transactionId = id;
|
||||
updateInternalTimeout();
|
||||
lock = new ReentrantLock();
|
||||
condition = lock.newCondition();
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------
|
||||
* PACKAGE API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abort the currently in progress transaction if any.
|
||||
*/
|
||||
void abort() {
|
||||
aborted = true;
|
||||
safeUnlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset handler state - clears command buffer + updates to new Transaction instance
|
||||
*
|
||||
* @param firestoreTransaction
|
||||
*/
|
||||
void resetState(Transaction firestoreTransaction) {
|
||||
this.commandBuffer = null;
|
||||
this.firestoreTransaction = firestoreTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that the transaction buffer has been received and needs to be processed.
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void signalBufferReceived(ReadableArray buffer) {
|
||||
lock.lock();
|
||||
|
||||
try {
|
||||
commandBuffer = buffer;
|
||||
condition.signalAll();
|
||||
} finally {
|
||||
safeUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for signalBufferReceived to signal condition
|
||||
*
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
void await() {
|
||||
lock.lock();
|
||||
|
||||
updateInternalTimeout();
|
||||
|
||||
try {
|
||||
while (!aborted && !timeout && !condition.await(10, TimeUnit.MILLISECONDS)) {
|
||||
if (System.currentTimeMillis() > timeoutAt) timeout = true;
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
// should never be interrupted
|
||||
} finally {
|
||||
safeUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current pending command buffer.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ReadableArray getCommandBuffer() {
|
||||
return commandBuffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get and resolve a DocumentSnapshot from transaction.get(ref);
|
||||
*
|
||||
* @param ref
|
||||
* @param promise
|
||||
*/
|
||||
void getDocument(DocumentReference ref, Promise promise) {
|
||||
updateInternalTimeout();
|
||||
|
||||
try {
|
||||
DocumentSnapshot documentSnapshot = firestoreTransaction.get(ref);
|
||||
WritableMap writableMap = FirestoreSerialize.snapshotToWritableMap(documentSnapshot);
|
||||
promise.resolve(writableMap);
|
||||
} catch (FirebaseFirestoreException firestoreException) {
|
||||
WritableMap jsError = RNFirebaseFirestore.getJSError(firestoreException);
|
||||
promise.reject(jsError.getString("code"), jsError.getString("message"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event map for `firestore_transaction_event` events.
|
||||
*
|
||||
* @param error
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
WritableMap createEventMap(@Nullable FirebaseFirestoreException error, String type) {
|
||||
WritableMap eventMap = Arguments.createMap();
|
||||
|
||||
eventMap.putInt("id", transactionId);
|
||||
eventMap.putString("appName", appName);
|
||||
|
||||
if (error != null) {
|
||||
eventMap.putString("type", "error");
|
||||
eventMap.putMap("error", RNFirebaseFirestore.getJSError(error));
|
||||
} else {
|
||||
eventMap.putString("type", type);
|
||||
}
|
||||
|
||||
return eventMap;
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------
|
||||
* INTERNAL API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
private void safeUnlock() {
|
||||
if (lock.isLocked()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInternalTimeout() {
|
||||
timeoutAt = System.currentTimeMillis() + 15000;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copy firebase lib into tests directory
|
||||
mkdir tests/firebase
|
||||
cp -R lib/* tests/firebase
|
||||
|
||||
# Install /tests npm packages
|
||||
cd tests && npm install
|
|
@ -18,6 +18,7 @@ static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed";
|
|||
static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved";
|
||||
|
||||
// Firestore
|
||||
static NSString *const FIRESTORE_TRANSACTION_EVENT = @"firestore_transaction_event";
|
||||
static NSString *const FIRESTORE_COLLECTION_SYNC_EVENT = @"firestore_collection_sync_event";
|
||||
static NSString *const FIRESTORE_DOCUMENT_SYNC_EVENT = @"firestore_document_sync_event";
|
||||
|
||||
|
|
|
@ -940,18 +940,12 @@ RCT_EXPORT_METHOD(unlink:
|
|||
@param RCTPromiseRejectBlock reject
|
||||
@return
|
||||
*/
|
||||
RCT_EXPORT_METHOD(reauthenticateWithCredential:
|
||||
(NSString *) appDisplayName
|
||||
provider:
|
||||
(NSString *) provider
|
||||
authToken:
|
||||
(NSString *) authToken
|
||||
authSecret:
|
||||
(NSString *) authSecret
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(reauthenticateWithCredential:(NSString *) appDisplayName
|
||||
provider:(NSString *) provider
|
||||
authToken:(NSString *) authToken
|
||||
authSecret:(NSString *) authSecret
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName];
|
||||
|
||||
FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret];
|
||||
|
@ -976,6 +970,45 @@ RCT_EXPORT_METHOD(reauthenticateWithCredential:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
reauthenticateAndRetrieveDataWithCredential
|
||||
|
||||
@param NSString provider
|
||||
@param NSString authToken
|
||||
@param NSString authSecret
|
||||
@param RCTPromiseResolveBlock resolve
|
||||
@param RCTPromiseRejectBlock reject
|
||||
@return
|
||||
*/
|
||||
RCT_EXPORT_METHOD(reauthenticateAndRetrieveDataWithCredential:(NSString *) appDisplayName
|
||||
provider:(NSString *) provider
|
||||
authToken:(NSString *) authToken
|
||||
authSecret:(NSString *) authSecret
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName];
|
||||
|
||||
FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret];
|
||||
|
||||
if (credential == nil) {
|
||||
return reject(@"auth/invalid-credential", @"The supplied auth credential is malformed, has expired or is not currently supported.", nil);
|
||||
}
|
||||
|
||||
FIRUser *user = [FIRAuth authWithApp:firApp].currentUser;
|
||||
|
||||
if (user) {
|
||||
[user reauthenticateAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
[self promiseRejectAuthException:reject error:error];
|
||||
} else {
|
||||
[self promiseWithAuthResult:resolve rejecter:reject authResult:authResult];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self promiseNoUser:resolve rejecter:reject isError:YES];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
fetchProvidersForEmail
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@ RCT_EXPORT_METHOD(fetch:
|
|||
(RCTPromiseRejectBlock) reject) {
|
||||
[[FIRRemoteConfig remoteConfig] fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) {
|
||||
if (error) {
|
||||
RCTLogError(@"\nError: %@", RCTJSErrorFromNSError(error));
|
||||
reject(convertFIRRemoteConfigFetchStatusToNSString(status), error.localizedDescription, error);
|
||||
} else {
|
||||
resolve(convertFIRRemoteConfigFetchStatusToNSString(status));
|
||||
|
@ -64,7 +63,6 @@ RCT_EXPORT_METHOD(fetchWithExpirationDuration:
|
|||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
[[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:expirationDuration.doubleValue completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *__nullable error) {
|
||||
if (error) {
|
||||
RCTLogError(@"\nError: %@", RCTJSErrorFromNSError(error));
|
||||
reject(convertFIRRemoteConfigFetchStatusToNSString(status), error.localizedDescription, error);
|
||||
} else {
|
||||
resolve(convertFIRRemoteConfigFetchStatusToNSString(status));
|
||||
|
|
|
@ -22,44 +22,44 @@ RCT_EXPORT_MODULE();
|
|||
return self;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(goOnline:(NSString *) appDisplayName) {
|
||||
RCT_EXPORT_METHOD(goOnline:(NSString *)appDisplayName) {
|
||||
[[RNFirebaseDatabase getDatabaseForApp:appDisplayName] goOnline];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(goOffline:(NSString *) appDisplayName) {
|
||||
RCT_EXPORT_METHOD(goOffline:(NSString *)appDisplayName) {
|
||||
[[RNFirebaseDatabase getDatabaseForApp:appDisplayName] goOffline];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setPersistence:(NSString *) appDisplayName
|
||||
state:(BOOL) state) {
|
||||
RCT_EXPORT_METHOD(setPersistence:(NSString *)appDisplayName
|
||||
state:(BOOL)state) {
|
||||
[RNFirebaseDatabase getDatabaseForApp:appDisplayName].persistenceEnabled = state;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setPersistenceCacheSizeBytes:(NSString *) appDisplayName
|
||||
size:(NSInteger *) size) {
|
||||
RCT_EXPORT_METHOD(setPersistenceCacheSizeBytes:(NSString *)appDisplayName
|
||||
size:(NSInteger *)size) {
|
||||
[RNFirebaseDatabase getDatabaseForApp:appDisplayName].persistenceCacheSizeBytes = (NSUInteger)size;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(enableLogging:(BOOL) enabled) {
|
||||
RCT_EXPORT_METHOD(enableLogging:(BOOL)enabled) {
|
||||
[FIRDatabase setLoggingEnabled:enabled];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(keepSynced:(NSString *) appDisplayName
|
||||
key:(NSString *) key
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
state:(BOOL) state) {
|
||||
RCT_EXPORT_METHOD(keepSynced:(NSString *)appDisplayName
|
||||
key:(NSString *)key
|
||||
path:(NSString *)path
|
||||
modifiers:(NSArray *)modifiers
|
||||
state:(BOOL)state) {
|
||||
FIRDatabaseQuery *query = [self getInternalReferenceForApp:appDisplayName key:key path:path modifiers:modifiers].query;
|
||||
[query keepSynced:state];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(transactionTryCommit:(NSString *) appDisplayName
|
||||
transactionId:(nonnull NSNumber *) transactionId
|
||||
updates:(NSDictionary *) updates) {
|
||||
RCT_EXPORT_METHOD(transactionTryCommit:(NSString *)appDisplayName
|
||||
transactionId:(nonnull NSNumber *)transactionId
|
||||
updates:(NSDictionary *)updates) {
|
||||
__block NSMutableDictionary *transactionState;
|
||||
|
||||
dispatch_sync(_transactionQueue, ^{
|
||||
transactionState = _transactions[transactionId];
|
||||
transactionState = _transactions[[transactionId stringValue]];
|
||||
});
|
||||
|
||||
if (!transactionState) {
|
||||
|
@ -82,171 +82,166 @@ RCT_EXPORT_METHOD(transactionTryCommit:(NSString *) appDisplayName
|
|||
}
|
||||
|
||||
|
||||
RCT_EXPORT_METHOD(transactionStart:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
transactionId:(nonnull NSNumber *) transactionId
|
||||
applyLocally:(BOOL) applyLocally) {
|
||||
RCT_EXPORT_METHOD(transactionStart:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
transactionId:(nonnull NSNumber *)transactionId
|
||||
applyLocally:(BOOL)applyLocally) {
|
||||
dispatch_async(_transactionQueue, ^{
|
||||
NSMutableDictionary *transactionState = [NSMutableDictionary new];
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
transactionState[@"semaphore"] = sema;
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
|
||||
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData *
|
||||
_Nonnull currentData) {
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions setValue:transactionState forKey:transactionId];
|
||||
NSDictionary *updateMap = [self createTransactionUpdateMap:appDisplayName transactionId:transactionId updatesData:currentData];
|
||||
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:updateMap];
|
||||
});
|
||||
[ref runTransactionBlock:^FIRTransactionResult *_Nonnull (FIRMutableData *_Nonnull currentData) {
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions setValue:transactionState forKey:[transactionId stringValue]];
|
||||
NSDictionary *updateMap = [self createTransactionUpdateMap:appDisplayName transactionId:transactionId updatesData:currentData];
|
||||
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:updateMap];
|
||||
});
|
||||
|
||||
// wait for the js event handler to call tryCommitTransaction
|
||||
// this wait occurs on the Firebase Worker Queue
|
||||
// so if the tryCommitTransaction fails to signal the semaphore
|
||||
// no further blocks will be executed by Firebase until the timeout expires
|
||||
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
|
||||
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
|
||||
// wait for the js event handler to call tryCommitTransaction
|
||||
// this wait occurs on the Firebase Worker Queue
|
||||
// so if the tryCommitTransaction fails to signal the semaphore
|
||||
// no further blocks will be executed by Firebase until the timeout expires
|
||||
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
|
||||
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
|
||||
|
||||
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
|
||||
id value = [transactionState valueForKey:@"value"];
|
||||
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
|
||||
id value = [transactionState valueForKey:@"value"];
|
||||
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions removeObjectForKey:transactionId];
|
||||
});
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions removeObjectForKey:[transactionId stringValue]];
|
||||
});
|
||||
|
||||
if (abort) {
|
||||
return [FIRTransactionResult abort];
|
||||
} else {
|
||||
currentData.value = value;
|
||||
return [FIRTransactionResult successWithValue:currentData];
|
||||
}
|
||||
}
|
||||
andCompletionBlock:
|
||||
^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
|
||||
if (abort) {
|
||||
return [FIRTransactionResult abort];
|
||||
} else {
|
||||
currentData.value = value;
|
||||
return [FIRTransactionResult successWithValue:currentData];
|
||||
}
|
||||
} andCompletionBlock:^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
|
||||
NSDictionary *resultMap = [self createTransactionResultMap:appDisplayName transactionId:transactionId error:databaseError committed:committed snapshot:snapshot];
|
||||
[RNFirebaseUtil sendJSEvent:self name:DATABASE_TRANSACTION_EVENT body:resultMap];
|
||||
}
|
||||
withLocalEvents:
|
||||
applyLocally];
|
||||
} withLocalEvents:applyLocally];
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectSet:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectSet:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
props:(NSDictionary *)props
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref onDisconnectSetValue:props[@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectUpdate:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectUpdate:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
props:(NSDictionary *)props
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref onDisconnectUpdateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref onDisconnectRemoveValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref cancelDisconnectOperationsWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(set:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(set:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
props:(NSDictionary *)props
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref setValue:[props valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setPriority:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
priority:(NSDictionary *) priority
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(setPriority:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
priority:(NSDictionary *)priority
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref setPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setWithPriority:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
data:(NSDictionary *) data
|
||||
priority:(NSDictionary *) priority
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(setWithPriority:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
data:(NSDictionary *)data
|
||||
priority:(NSDictionary *)priority
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref setValue:[data valueForKey:@"value"] andPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(update:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(update:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
props:(NSDictionary *)props
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref updateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(remove:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(remove:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appDisplayName path:path];
|
||||
[ref removeValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(once:(NSString *) appDisplayName
|
||||
key:(NSString *) key
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
eventName:(NSString *) eventName
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(once:(NSString *)appDisplayName
|
||||
key:(NSString *)key
|
||||
path:(NSString *)path
|
||||
modifiers:(NSArray *)modifiers
|
||||
eventName:(NSString *)eventName
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appDisplayName key:key path:path modifiers:modifiers];
|
||||
[ref once:eventName resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(on:(NSString *) appDisplayName
|
||||
props:(NSDictionary *) props) {
|
||||
RCT_EXPORT_METHOD(on:(NSString *)appDisplayName
|
||||
props:(NSDictionary *)props) {
|
||||
RNFirebaseDatabaseReference *ref = [self getCachedInternalReferenceForApp:appDisplayName props:props];
|
||||
[ref on:props[@"eventType"] registration:props[@"registration"]];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(off:(NSString *) key
|
||||
eventRegistrationKey:(NSString *) eventRegistrationKey) {
|
||||
RCT_EXPORT_METHOD(off:(NSString *)key
|
||||
eventRegistrationKey:(NSString *)eventRegistrationKey) {
|
||||
RNFirebaseDatabaseReference *ref = _dbReferences[key];
|
||||
if (ref) {
|
||||
[ref removeEventListener:eventRegistrationKey];
|
||||
|
@ -331,7 +326,7 @@ RCT_EXPORT_METHOD(off:(NSString *) key
|
|||
message = [RNFirebaseDatabase getMessageWithService:@"The write was canceled by the user." service:service fullCode:code];
|
||||
break;
|
||||
|
||||
// TODO: Missing iOS equivalent codes
|
||||
// TODO: Missing iOS equivalent codes
|
||||
case -1:
|
||||
code = [RNFirebaseDatabase getCodeWithService:service code:@"data-stale"];
|
||||
message = [RNFirebaseDatabase getMessageWithService:@"The transaction needs to be run again with current data." service:service fullCode:code];
|
||||
|
@ -413,8 +408,7 @@ RCT_EXPORT_METHOD(off:(NSString *) key
|
|||
return @[DATABASE_SYNC_EVENT, DATABASE_TRANSACTION_EVENT];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -424,3 +418,7 @@ RCT_EXPORT_METHOD(off:(NSString *) key
|
|||
@implementation RNFirebaseDatabase
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
#if __has_include(<FirebaseDatabase/FIRDatabase.h>)
|
||||
|
||||
- (id)initWithPathAndModifiers:(RCTEventEmitter *)emitter
|
||||
appDisplayName:(NSString *) appDisplayName
|
||||
key:(NSString *) key
|
||||
refPath:(NSString *) refPath
|
||||
modifiers:(NSArray *) modifiers {
|
||||
appDisplayName:(NSString *)appDisplayName
|
||||
key:(NSString *)key
|
||||
refPath:(NSString *)refPath
|
||||
modifiers:(NSArray *)modifiers {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_emitter = emitter;
|
||||
|
@ -22,14 +22,14 @@
|
|||
}
|
||||
|
||||
- (void)removeEventListener:(NSString *)eventRegistrationKey {
|
||||
FIRDatabaseHandle handle = (FIRDatabaseHandle) [_listeners[eventRegistrationKey] integerValue];
|
||||
FIRDatabaseHandle handle = (FIRDatabaseHandle)[_listeners[eventRegistrationKey] integerValue];
|
||||
if (handle) {
|
||||
[_query removeObserverWithHandle:handle];
|
||||
[_listeners removeObjectForKey:eventRegistrationKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)on:(NSString *) eventType registration:(NSDictionary *) registration {
|
||||
- (void)on:(NSString *)eventType registration:(NSDictionary *)registration {
|
||||
NSString *eventRegistrationKey = registration[@"eventRegistrationKey"];
|
||||
if (![self hasEventListener:eventRegistrationKey]) {
|
||||
id andPreviousSiblingKeyWithBlock = ^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) {
|
||||
|
@ -46,9 +46,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)once:(NSString *) eventType
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject {
|
||||
- (void)once:(NSString *)eventType
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject {
|
||||
FIRDataEventType firDataEventType = (FIRDataEventType)[self eventTypeFromName:eventType];
|
||||
[_query observeSingleEventOfType:firDataEventType andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) {
|
||||
NSDictionary *data = [RNFirebaseDatabaseReference snapshotToDictionary:snapshot previousChildName:previousChildName];
|
||||
|
@ -59,33 +59,33 @@
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)handleDatabaseEvent:(NSString *) eventType
|
||||
registration:(NSDictionary *) registration
|
||||
dataSnapshot:(FIRDataSnapshot *) dataSnapshot
|
||||
previousChildName:(NSString *) previousChildName {
|
||||
- (void)handleDatabaseEvent:(NSString *)eventType
|
||||
registration:(NSDictionary *)registration
|
||||
dataSnapshot:(FIRDataSnapshot *)dataSnapshot
|
||||
previousChildName:(NSString *)previousChildName {
|
||||
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary *data = [RNFirebaseDatabaseReference snapshotToDictionary:dataSnapshot previousChildName:previousChildName];
|
||||
|
||||
|
||||
[event setValue:data forKey:@"data"];
|
||||
[event setValue:_key forKey:@"key"];
|
||||
[event setValue:eventType forKey:@"eventType"];
|
||||
[event setValue:registration forKey:@"registration"];
|
||||
|
||||
|
||||
[RNFirebaseUtil sendJSEvent:self.emitter name:DATABASE_SYNC_EVENT body:event];
|
||||
}
|
||||
|
||||
- (void)handleDatabaseError:(NSDictionary *) registration
|
||||
- (void)handleDatabaseError:(NSDictionary *)registration
|
||||
error:(NSError *)error {
|
||||
NSMutableDictionary *event = [[NSMutableDictionary alloc] init];
|
||||
[event setValue:_key forKey:@"key"];
|
||||
[event setValue:[RNFirebaseDatabase getJSError:error] forKey:@"error"];
|
||||
[event setValue:registration forKey:@"registration"];
|
||||
|
||||
|
||||
[RNFirebaseUtil sendJSEvent:self.emitter name:DATABASE_SYNC_EVENT body:event];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)snapshotToDictionary:(FIRDataSnapshot *) dataSnapshot
|
||||
previousChildName:(NSString *) previousChildName {
|
||||
+ (NSDictionary *)snapshotToDictionary:(FIRDataSnapshot *)dataSnapshot
|
||||
previousChildName:(NSString *)previousChildName {
|
||||
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary *snapshot = [RNFirebaseDatabaseReference snapshotToDict:dataSnapshot];
|
||||
|
||||
|
@ -97,7 +97,7 @@
|
|||
|
||||
+ (NSDictionary *)snapshotToDict:(FIRDataSnapshot *)dataSnapshot {
|
||||
NSMutableDictionary *snapshot = [[NSMutableDictionary alloc] init];
|
||||
|
||||
|
||||
[snapshot setValue:dataSnapshot.key forKey:@"key"];
|
||||
[snapshot setValue:@(dataSnapshot.exists) forKey:@"exists"];
|
||||
[snapshot setValue:@(dataSnapshot.hasChildren) forKey:@"hasChildren"];
|
||||
|
@ -105,11 +105,11 @@
|
|||
[snapshot setValue:[RNFirebaseDatabaseReference getChildKeys:dataSnapshot] forKey:@"childKeys"];
|
||||
[snapshot setValue:dataSnapshot.priority forKey:@"priority"];
|
||||
[snapshot setValue:dataSnapshot.value forKey:@"value"];
|
||||
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
+ (NSMutableArray *) getChildKeys:(FIRDataSnapshot *) snapshot {
|
||||
+ (NSMutableArray *)getChildKeys:(FIRDataSnapshot *)snapshot {
|
||||
NSMutableArray *childKeys = [NSMutableArray array];
|
||||
if (snapshot.childrenCount > 0) {
|
||||
NSEnumerator *children = [snapshot children];
|
||||
|
@ -121,7 +121,7 @@
|
|||
return childKeys;
|
||||
}
|
||||
|
||||
- (FIRDatabaseQuery *)buildQueryAtPathWithModifiers:(NSString *) path
|
||||
- (FIRDatabaseQuery *)buildQueryAtPathWithModifiers:(NSString *)path
|
||||
modifiers:(NSArray *)modifiers {
|
||||
FIRDatabase *firebaseDatabase = [RNFirebaseDatabase getDatabaseForApp:_appDisplayName];
|
||||
FIRDatabaseQuery *query = [[firebaseDatabase reference] child:path];
|
||||
|
@ -186,7 +186,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasEventListener:(NSString *) eventRegistrationKey {
|
||||
- (BOOL)hasEventListener:(NSString *)eventRegistrationKey {
|
||||
return _listeners[eventRegistrationKey] != nil;
|
||||
}
|
||||
|
||||
|
@ -214,3 +214,4 @@
|
|||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
@interface RNFirebaseFirestore : RCTEventEmitter <RCTBridgeModule> {}
|
||||
@property NSMutableDictionary *transactions;
|
||||
@property dispatch_queue_t transactionQueue;
|
||||
|
||||
+ (void)promiseRejectException:(RCTPromiseRejectBlock)reject error:(NSError *)error;
|
||||
|
||||
|
|
|
@ -13,49 +13,217 @@ RCT_EXPORT_MODULE();
|
|||
- (id)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
|
||||
_transactions = [[NSMutableDictionary alloc] init];
|
||||
_transactionQueue = dispatch_queue_create("io.invertase.react-native-firebase.firestore", DISPATCH_QUEUE_CONCURRENT);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(enableLogging:(BOOL) enabled) {
|
||||
/**
|
||||
* TRANSACTIONS
|
||||
*/
|
||||
|
||||
RCT_EXPORT_METHOD(transactionGetDocument:(NSString *)appDisplayName
|
||||
transactionId:(nonnull NSNumber *)transactionId
|
||||
path:(NSString *)path
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
__block NSMutableDictionary *transactionState;
|
||||
|
||||
dispatch_sync(_transactionQueue, ^{
|
||||
transactionState = _transactions[[transactionId stringValue]];
|
||||
});
|
||||
|
||||
if (!transactionState) {
|
||||
NSLog(@"transactionGetDocument called for non-existant transactionId %@", transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
FIRTransaction *transaction = [transactionState valueForKey:@"transaction"];
|
||||
FIRDocumentReference *ref = [self getDocumentForAppPath:appDisplayName path:path].ref;
|
||||
FIRDocumentSnapshot *snapshot = [transaction getDocument:ref error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
[RNFirebaseFirestore promiseRejectException:reject error:error];
|
||||
} else {
|
||||
NSDictionary *snapshotDict = [RNFirebaseFirestoreDocumentReference snapshotToDictionary:snapshot];
|
||||
NSString *path = snapshotDict[@"path"];
|
||||
if (path == nil) {
|
||||
[snapshotDict setValue:ref.path forKey:@"path"];
|
||||
}
|
||||
resolve(snapshotDict);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(transactionDispose:(NSString *)appDisplayName
|
||||
transactionId:(nonnull NSNumber *)transactionId) {
|
||||
__block NSMutableDictionary *transactionState;
|
||||
|
||||
dispatch_sync(_transactionQueue, ^{
|
||||
transactionState = _transactions[[transactionId stringValue]];
|
||||
});
|
||||
|
||||
if (!transactionState) {
|
||||
NSLog(@"transactionGetDocument called for non-existant transactionId %@", transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_semaphore_t semaphore = [transactionState valueForKey:@"semaphore"];
|
||||
[transactionState setValue:@true forKey:@"abort"];
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(transactionApplyBuffer:(NSString *)appDisplayName
|
||||
transactionId:(nonnull NSNumber *)transactionId
|
||||
commandBuffer:(NSArray *)commandBuffer) {
|
||||
__block NSMutableDictionary *transactionState;
|
||||
|
||||
dispatch_sync(_transactionQueue, ^{
|
||||
transactionState = _transactions[[transactionId stringValue]];
|
||||
});
|
||||
|
||||
if (!transactionState) {
|
||||
NSLog(@"transactionGetDocument called for non-existant transactionId %@", transactionId);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_semaphore_t semaphore = [transactionState valueForKey:@"semaphore"];
|
||||
[transactionState setValue:commandBuffer forKey:@"commandBuffer"];
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(transactionBegin:(NSString *)appDisplayName
|
||||
transactionId:(nonnull NSNumber *)transactionId) {
|
||||
FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:appDisplayName];
|
||||
__block BOOL aborted = false;
|
||||
|
||||
dispatch_async(_transactionQueue, ^{
|
||||
NSMutableDictionary *transactionState = [NSMutableDictionary new];
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
transactionState[@"semaphore"] = semaphore;
|
||||
|
||||
[firestore runTransactionWithBlock:^id (FIRTransaction *transaction, NSError * *errorPointer) {
|
||||
transactionState[@"transaction"] = transaction;
|
||||
|
||||
// Build and send transaction update event
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions setValue:transactionState forKey:[transactionId stringValue]];
|
||||
NSMutableDictionary *eventMap = [NSMutableDictionary new];
|
||||
eventMap[@"type"] = @"update";
|
||||
eventMap[@"id"] = transactionId;
|
||||
eventMap[@"appName"] = appDisplayName;
|
||||
[RNFirebaseUtil sendJSEvent:self name:FIRESTORE_TRANSACTION_EVENT body:eventMap];
|
||||
});
|
||||
|
||||
// wait for the js event handler to call transactionApplyBuffer
|
||||
// this wait occurs on the RNFirestore Worker Queue so if transactionApplyBuffer fails to
|
||||
// signal the semaphore then no further blocks will be executed by RNFirestore until the timeout expires
|
||||
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 3000 * NSEC_PER_SEC);
|
||||
|
||||
BOOL timedOut = dispatch_semaphore_wait(semaphore, delayTime) != 0;
|
||||
aborted = [transactionState valueForKey:@"abort"];
|
||||
|
||||
// dispose of transaction dictionary
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions removeObjectForKey:[transactionId stringValue]];
|
||||
});
|
||||
|
||||
if (aborted) {
|
||||
*errorPointer = [NSError errorWithDomain:FIRFirestoreErrorDomain code:FIRFirestoreErrorCodeAborted userInfo:@{}];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (timedOut) {
|
||||
*errorPointer = [NSError errorWithDomain:FIRFirestoreErrorDomain code:FIRFirestoreErrorCodeDeadlineExceeded userInfo:@{}];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *commandBuffer = [transactionState valueForKey:@"commandBuffer"];
|
||||
for (NSDictionary *command in commandBuffer) {
|
||||
NSString *type = command[@"type"];
|
||||
NSString *path = command[@"path"];
|
||||
NSDictionary *data = [RNFirebaseFirestoreDocumentReference parseJSMap:firestore jsMap:command[@"data"]];
|
||||
|
||||
FIRDocumentReference *ref = [firestore documentWithPath:path];
|
||||
|
||||
if ([type isEqualToString:@"delete"]) {
|
||||
[transaction deleteDocument:ref];
|
||||
} else if ([type isEqualToString:@"set"]) {
|
||||
NSDictionary *options = command[@"options"];
|
||||
if (options && options[@"merge"]) {
|
||||
[transaction setData:data forDocument:ref options:[FIRSetOptions merge]];
|
||||
} else {
|
||||
[transaction setData:data forDocument:ref];
|
||||
}
|
||||
} else if ([type isEqualToString:@"update"]) {
|
||||
[transaction updateData:data forDocument:ref];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
} completion:^(id result, NSError *error) {
|
||||
if (aborted == NO) {
|
||||
NSMutableDictionary *eventMap = [NSMutableDictionary new];
|
||||
eventMap[@"id"] = transactionId;
|
||||
eventMap[@"appName"] = appDisplayName;
|
||||
|
||||
if (error != nil) {
|
||||
eventMap[@"type"] = @"error";
|
||||
eventMap[@"error"] = [RNFirebaseFirestore getJSError:error];
|
||||
} else {
|
||||
eventMap[@"type"] = @"complete";
|
||||
}
|
||||
|
||||
[RNFirebaseUtil sendJSEvent:self name:FIRESTORE_TRANSACTION_EVENT body:eventMap];
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TRANSACTIONS END
|
||||
*/
|
||||
|
||||
|
||||
RCT_EXPORT_METHOD(enableLogging:(BOOL)enabled) {
|
||||
[FIRFirestore enableLogging:enabled];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(collectionGet:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
filters:(NSArray *) filters
|
||||
orders:(NSArray *) orders
|
||||
options:(NSDictionary *) options
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(collectionGet:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
filters:(NSArray *)filters
|
||||
orders:(NSArray *)orders
|
||||
options:(NSDictionary *)options
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
[[self getCollectionForAppPath:appDisplayName path:path filters:filters orders:orders options:options] get:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(collectionOffSnapshot:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
filters:(NSArray *) filters
|
||||
orders:(NSArray *) orders
|
||||
options:(NSDictionary *) options
|
||||
listenerId:(nonnull NSString *) listenerId) {
|
||||
RCT_EXPORT_METHOD(collectionOffSnapshot:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
filters:(NSArray *)filters
|
||||
orders:(NSArray *)orders
|
||||
options:(NSDictionary *)options
|
||||
listenerId:(nonnull NSString *)listenerId) {
|
||||
[RNFirebaseFirestoreCollectionReference offSnapshot:listenerId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
filters:(NSArray *) filters
|
||||
orders:(NSArray *) orders
|
||||
options:(NSDictionary *) options
|
||||
listenerId:(nonnull NSString *) listenerId
|
||||
queryListenOptions:(NSDictionary *) queryListenOptions) {
|
||||
RCT_EXPORT_METHOD(collectionOnSnapshot:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
filters:(NSArray *)filters
|
||||
orders:(NSArray *)orders
|
||||
options:(NSDictionary *)options
|
||||
listenerId:(nonnull NSString *)listenerId
|
||||
queryListenOptions:(NSDictionary *)queryListenOptions) {
|
||||
RNFirebaseFirestoreCollectionReference *ref = [self getCollectionForAppPath:appDisplayName path:path filters:filters orders:orders options:options];
|
||||
[ref onSnapshot:listenerId queryListenOptions:queryListenOptions];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentBatch:(NSString *) appDisplayName
|
||||
writes:(NSArray *) writes
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(documentBatch:(NSString *)appDisplayName
|
||||
writes:(NSArray *)writes
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:appDisplayName];
|
||||
FIRWriteBatch *batch = [firestore batch];
|
||||
|
||||
|
@ -80,7 +248,7 @@ RCT_EXPORT_METHOD(documentBatch:(NSString *) appDisplayName
|
|||
}
|
||||
}
|
||||
|
||||
[batch commitWithCompletion:^(NSError * _Nullable error) {
|
||||
[batch commitWithCompletion:^(NSError *_Nullable error) {
|
||||
if (error) {
|
||||
[RNFirebaseFirestore promiseRejectException:reject error:error];
|
||||
} else {
|
||||
|
@ -89,55 +257,55 @@ RCT_EXPORT_METHOD(documentBatch:(NSString *) appDisplayName
|
|||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentDelete:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(documentDelete:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
[[self getDocumentForAppPath:appDisplayName path:path] delete:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentGet:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(documentGet:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
[[self getDocumentForAppPath:appDisplayName path:path] get:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentGetAll:(NSString *) appDisplayName
|
||||
documents:(NSString *) documents
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(documentGetAll:(NSString *)appDisplayName
|
||||
documents:(NSString *)documents
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
// Not supported on iOS out of the box
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
listenerId:(nonnull NSString *) listenerId) {
|
||||
RCT_EXPORT_METHOD(documentOffSnapshot:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
listenerId:(nonnull NSString *)listenerId) {
|
||||
[RNFirebaseFirestoreDocumentReference offSnapshot:listenerId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
listenerId:(nonnull NSString *) listenerId
|
||||
docListenOptions:(NSDictionary *) docListenOptions) {
|
||||
RCT_EXPORT_METHOD(documentOnSnapshot:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
listenerId:(nonnull NSString *)listenerId
|
||||
docListenOptions:(NSDictionary *)docListenOptions) {
|
||||
RNFirebaseFirestoreDocumentReference *ref = [self getDocumentForAppPath:appDisplayName path:path];
|
||||
[ref onSnapshot:listenerId docListenOptions:docListenOptions];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentSet:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
data:(NSDictionary *) data
|
||||
options:(NSDictionary *) options
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(documentSet:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
data:(NSDictionary *)data
|
||||
options:(NSDictionary *)options
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
[[self getDocumentForAppPath:appDisplayName path:path] set:data options:options resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(documentUpdate:(NSString *) appDisplayName
|
||||
path:(NSString *) path
|
||||
data:(NSDictionary *) data
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(documentUpdate:(NSString *)appDisplayName
|
||||
path:(NSString *)path
|
||||
data:(NSDictionary *)data
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
[[self getDocumentForAppPath:appDisplayName path:path] update:data resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
||||
|
@ -262,11 +430,10 @@ RCT_EXPORT_METHOD(documentUpdate:(NSString *) appDisplayName
|
|||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[FIRESTORE_COLLECTION_SYNC_EVENT, FIRESTORE_DOCUMENT_SYNC_EVENT];
|
||||
return @[FIRESTORE_COLLECTION_SYNC_EVENT, FIRESTORE_DOCUMENT_SYNC_EVENT, FIRESTORE_TRANSACTION_EVENT];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
@ -276,3 +443,4 @@ RCT_EXPORT_METHOD(documentUpdate:(NSString *) appDisplayName
|
|||
@implementation RNFirebaseFirestore
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { isString, isObject } from '../../utils';
|
||||
|
||||
import type App from '../core/app';
|
||||
|
||||
|
@ -44,23 +45,37 @@ export default class Analytics extends ModuleBase {
|
|||
* @return {Promise}
|
||||
*/
|
||||
logEvent(name: string, params: Object = {}): void {
|
||||
if (!isString(name)) {
|
||||
throw new Error(
|
||||
`analytics.logEvent(): First argument 'name' is required and must be a string value.`
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof params !== 'undefined' && !isObject(params)) {
|
||||
throw new Error(
|
||||
`analytics.logEvent(): Second optional argument 'params' must be an object if provided.`
|
||||
);
|
||||
}
|
||||
|
||||
// check name is not a reserved event name
|
||||
if (ReservedEventNames.includes(name)) {
|
||||
throw new Error(
|
||||
`event name '${name}' is a reserved event name and can not be used.`
|
||||
`analytics.logEvent(): event name '${name}' is a reserved event name and can not be used.`
|
||||
);
|
||||
}
|
||||
|
||||
// name format validation
|
||||
if (!AlphaNumericUnderscore.test(name)) {
|
||||
throw new Error(
|
||||
`Event name '${name}' is invalid. Names should contain 1 to 32 alphanumeric characters or underscores.`
|
||||
`analytics.logEvent(): Event name '${name}' is invalid. Names should contain 1 to 32 alphanumeric characters or underscores.`
|
||||
);
|
||||
}
|
||||
|
||||
// maximum number of allowed params check
|
||||
if (params && Object.keys(params).length > 25)
|
||||
throw new Error('Maximum number of parameters exceeded (25).');
|
||||
throw new Error(
|
||||
'analytics.logEvent(): Maximum number of parameters exceeded (25).'
|
||||
);
|
||||
|
||||
// Parameter names can be up to 24 characters long and must start with an alphabetic character
|
||||
// and contain only alphanumeric characters and underscores. Only String, long and double param
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class PhoneAuthListener {
|
|||
const type = events[i];
|
||||
SharedEventEmitter.once(
|
||||
this._internalEvents[type],
|
||||
// $FlowBug: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
// $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
this[`_${type}Handler`].bind(this)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ export default class Auth extends ModuleBase {
|
|||
createUserAndRetrieveDataWithEmailAndPassword(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<User> {
|
||||
): Promise<UserCredential> {
|
||||
return getNativeModule(this)
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, password)
|
||||
.then(userCredential => this._setUserCredential(userCredential));
|
||||
|
|
|
@ -123,7 +123,7 @@ export default class RemoteConfig extends ModuleBase {
|
|||
* "source" : OneOf<String>(remoteConfigSourceRemote|remoteConfigSourceDefault|remoteConfigSourceStatic)
|
||||
* }
|
||||
*/
|
||||
getValue(key: String) {
|
||||
getValue(key: string) {
|
||||
return getNativeModule(this)
|
||||
.getValue(key || '')
|
||||
.then(this._nativeValueToJS);
|
||||
|
@ -160,7 +160,7 @@ export default class RemoteConfig extends ModuleBase {
|
|||
* @param prefix: The key prefix to look for. If prefix is nil or empty, returns all the keys.
|
||||
* @returns {*|Promise.<Array<String>>}
|
||||
*/
|
||||
getKeysByPrefix(prefix?: String) {
|
||||
getKeysByPrefix(prefix?: string) {
|
||||
return getNativeModule(this).getKeysByPrefix(prefix);
|
||||
}
|
||||
|
||||
|
@ -176,7 +176,7 @@ export default class RemoteConfig extends ModuleBase {
|
|||
* Sets default configs from plist for default namespace;
|
||||
* @param resource: The plist file name or resource ID
|
||||
*/
|
||||
setDefaultsFromResource(resource: String | number) {
|
||||
setDefaultsFromResource(resource: string | number) {
|
||||
getNativeModule(this).setDefaultsFromResource(resource);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,10 +128,12 @@ export default class App {
|
|||
* @param props
|
||||
*/
|
||||
extendApp(props: Object) {
|
||||
if (!isObject(props))
|
||||
if (!isObject(props)) {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_MISSING_ARG('Object', 'extendApp')
|
||||
);
|
||||
}
|
||||
|
||||
const keys = Object.keys(props);
|
||||
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
|
@ -141,7 +143,7 @@ export default class App {
|
|||
throw new Error(INTERNALS.STRINGS.ERROR_PROTECTED_PROP(key));
|
||||
}
|
||||
|
||||
// $FlowBug: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
// $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
this[key] = props[key];
|
||||
this._extendedProps[key] = true;
|
||||
}
|
||||
|
@ -179,4 +181,13 @@ export default class App {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* toString returns the name of the app.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
toString() {
|
||||
return this._name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/**
|
||||
|
||||
* @flow
|
||||
*/
|
||||
import { NativeModules } from 'react-native';
|
||||
|
|
|
@ -317,9 +317,9 @@ export default class Reference extends ReferenceBase {
|
|||
if (isFunction(onComplete)) {
|
||||
return (
|
||||
promise
|
||||
// $FlowBug: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
// $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
.then(() => onComplete(null, newRef))
|
||||
// $FlowBug: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
// $FlowExpectedError: Reports that onComplete can change to null despite the null check: https://github.com/facebook/flow/issues/1655
|
||||
.catch(error => onComplete(error, null))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,14 +21,13 @@ const generateTransactionId = (): number => transactionId++;
|
|||
*/
|
||||
export default class TransactionHandler {
|
||||
_database: Database;
|
||||
_transactionListener: Function;
|
||||
_transactions: { [number]: Object };
|
||||
|
||||
constructor(database: Database) {
|
||||
this._transactions = {};
|
||||
this._database = database;
|
||||
|
||||
this._transactionListener = SharedEventEmitter.addListener(
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._database, 'database_transaction_event'),
|
||||
this._handleTransactionEvent.bind(this)
|
||||
);
|
||||
|
|
|
@ -96,7 +96,7 @@ export default class DocumentReference {
|
|||
'DocumentReference.onSnapshot failed: Second argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowBug: Not coping with the overloaded method signature
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext,
|
||||
error: observerOrOnNextOrOnError,
|
||||
|
@ -116,7 +116,7 @@ export default class DocumentReference {
|
|||
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowBug: Not coping with the overloaded method signature
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext.next,
|
||||
error: optionsOrObserverOrOnNext.error,
|
||||
|
@ -140,7 +140,7 @@ export default class DocumentReference {
|
|||
'DocumentReference.onSnapshot failed: Third argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowBug: Not coping with the overloaded method signature
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: observerOrOnNextOrOnError,
|
||||
error: onError,
|
||||
|
|
|
@ -183,7 +183,7 @@ export default class Query {
|
|||
'Query.onSnapshot failed: Second argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowBug: Not coping with the overloaded method signature
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext,
|
||||
error: observerOrOnNextOrOnError,
|
||||
|
@ -203,7 +203,7 @@ export default class Query {
|
|||
'Query.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowBug: Not coping with the overloaded method signature
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: optionsOrObserverOrOnNext.next,
|
||||
error: optionsOrObserverOrOnNext.error,
|
||||
|
@ -231,7 +231,7 @@ export default class Query {
|
|||
'Query.onSnapshot failed: Third argument must be a valid function.'
|
||||
);
|
||||
}
|
||||
// $FlowBug: Not coping with the overloaded method signature
|
||||
// $FlowExpectedError: Not coping with the overloaded method signature
|
||||
observer = {
|
||||
next: observerOrOnNextOrOnError,
|
||||
error: onError,
|
||||
|
@ -322,7 +322,12 @@ export default class Query {
|
|||
// validate.isFieldPath('fieldPath', fieldPath);
|
||||
// validate.isOptionalFieldOrder('directionStr', directionStr);
|
||||
|
||||
if (this._queryOptions.startAt || this._queryOptions.endAt) {
|
||||
if (
|
||||
this._queryOptions.startAt ||
|
||||
this._queryOptions.startAfter ||
|
||||
this._queryOptions.endAt ||
|
||||
this._queryOptions.endBefore
|
||||
) {
|
||||
throw new Error(
|
||||
'Cannot specify an orderBy() constraint after calling ' +
|
||||
'startAt(), startAfter(), endBefore() or endAt().'
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* @flow
|
||||
* Firestore Transaction representation wrapper
|
||||
*/
|
||||
import { mergeFieldPathData } from './utils';
|
||||
import { buildNativeMap } from './utils/serialize';
|
||||
|
||||
import type Firestore from './';
|
||||
import type { TransactionMeta } from './TransactionHandler';
|
||||
import type DocumentReference from './DocumentReference';
|
||||
import DocumentSnapshot from './DocumentSnapshot';
|
||||
import { isObject, isString } from '../../utils';
|
||||
import FieldPath from './FieldPath';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
|
||||
type Command = {
|
||||
type: 'set' | 'update' | 'delete',
|
||||
path: string,
|
||||
data: ?{ [string]: any },
|
||||
options: ?{ merge: boolean },
|
||||
};
|
||||
|
||||
type SetOptions = {
|
||||
merge: boolean,
|
||||
};
|
||||
|
||||
// TODO docs state all get requests must be made FIRST before any modifications
|
||||
// TODO so need to validate that
|
||||
|
||||
/**
|
||||
* @class Transaction
|
||||
*/
|
||||
export default class Transaction {
|
||||
_pendingResult: ?any;
|
||||
_firestore: Firestore;
|
||||
_meta: TransactionMeta;
|
||||
_commandBuffer: Array<Command>;
|
||||
|
||||
constructor(firestore: Firestore, meta: TransactionMeta) {
|
||||
this._meta = meta;
|
||||
this._commandBuffer = [];
|
||||
this._firestore = firestore;
|
||||
this._pendingResult = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* INTERNAL API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clears the command buffer and any pending result in prep for
|
||||
* the next transaction iteration attempt.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_prepare() {
|
||||
this._commandBuffer = [];
|
||||
this._pendingResult = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* PUBLIC API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reads the document referenced by the provided DocumentReference.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be retrieved. Value must not be null.
|
||||
*
|
||||
* @returns Promise<DocumentSnapshot>
|
||||
*/
|
||||
get(documentRef: DocumentReference): Promise<DocumentSnapshot> {
|
||||
// todo validate doc ref
|
||||
return getNativeModule(this._firestore)
|
||||
.transactionGetDocument(this._meta.id, documentRef.path)
|
||||
.then(result => new DocumentSnapshot(this._firestore, result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the document referred to by the provided DocumentReference.
|
||||
* If the document does not exist yet, it will be created. If you pass options,
|
||||
* the provided data can be merged into the existing document.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be created. Value must not be null.
|
||||
* @param data Object An object of the fields and values for the document.
|
||||
* @param options SetOptions An object to configure the set behavior.
|
||||
* Pass {merge: true} to only replace the values specified in the data argument.
|
||||
* Fields omitted will remain untouched.
|
||||
*
|
||||
* @returns {Transaction}
|
||||
*/
|
||||
set(
|
||||
documentRef: DocumentReference,
|
||||
data: Object,
|
||||
options?: SetOptions
|
||||
): Transaction {
|
||||
// todo validate doc ref
|
||||
// todo validate data is object
|
||||
this._commandBuffer.push({
|
||||
type: 'set',
|
||||
path: documentRef.path,
|
||||
data: buildNativeMap(data),
|
||||
options: options || {},
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates fields in the document referred to by this DocumentReference.
|
||||
* The update will fail if applied to a document that does not exist. Nested
|
||||
* fields can be updated by providing dot-separated field path strings or by providing FieldPath objects.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be updated. Value must not be null.
|
||||
* @param args any Either an object containing all of the fields and values to update,
|
||||
* or a series of arguments alternating between fields (as string or FieldPath
|
||||
* objects) and values.
|
||||
*
|
||||
* @returns {Transaction}
|
||||
*/
|
||||
update(documentRef: DocumentReference, ...args: Array<any>): Transaction {
|
||||
// todo validate doc ref
|
||||
let data = {};
|
||||
if (args.length === 1) {
|
||||
if (!isObject(args[0])) {
|
||||
throw new Error(
|
||||
'Transaction.update failed: If using a single data argument, it must be an object.'
|
||||
);
|
||||
}
|
||||
|
||||
[data] = args;
|
||||
} else if (args.length % 2 === 1) {
|
||||
throw new Error(
|
||||
'Transaction.update failed: Must have either a single object data argument, or equal numbers of data key/value pairs.'
|
||||
);
|
||||
} else {
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
const key = args[i];
|
||||
const value = args[i + 1];
|
||||
if (isString(key)) {
|
||||
data[key] = value;
|
||||
} else if (key instanceof FieldPath) {
|
||||
data = mergeFieldPathData(data, key._segments, value);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Transaction.update failed: Argument at index ${i} must be a string or FieldPath`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._commandBuffer.push({
|
||||
type: 'update',
|
||||
path: documentRef.path,
|
||||
data: buildNativeMap(data),
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the document referred to by the provided DocumentReference.
|
||||
*
|
||||
* @param documentRef DocumentReference A reference to the document to be deleted. Value must not be null.
|
||||
*
|
||||
* @returns {Transaction}
|
||||
*/
|
||||
delete(documentRef: DocumentReference): Transaction {
|
||||
// todo validate doc ref
|
||||
this._commandBuffer.push({
|
||||
type: 'delete',
|
||||
path: documentRef.path,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* @flow
|
||||
* Firestore Transaction representation wrapper
|
||||
*/
|
||||
import { getAppEventName, SharedEventEmitter } from '../../utils/events';
|
||||
import { getLogger } from '../../utils/log';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import Transaction from './Transaction';
|
||||
import type Firestore from './';
|
||||
|
||||
let transactionId = 0;
|
||||
|
||||
/**
|
||||
* Uses the push id generator to create a transaction id
|
||||
* @returns {number}
|
||||
* @private
|
||||
*/
|
||||
const generateTransactionId = (): number => transactionId++;
|
||||
|
||||
export type TransactionMeta = {
|
||||
id: number,
|
||||
stack: Array<string>,
|
||||
reject: null | Function,
|
||||
resolve: null | Function,
|
||||
transaction: Transaction,
|
||||
updateFunction: (transaction: Transaction) => Promise<any>,
|
||||
};
|
||||
|
||||
type TransactionEvent = {
|
||||
id: number,
|
||||
type: 'update' | 'error' | 'complete',
|
||||
error: ?{ code: string, message: string },
|
||||
};
|
||||
|
||||
/**
|
||||
* @class TransactionHandler
|
||||
*/
|
||||
export default class TransactionHandler {
|
||||
_firestore: Firestore;
|
||||
_pending: { [number]: TransactionMeta };
|
||||
|
||||
constructor(firestore: Firestore) {
|
||||
this._pending = {};
|
||||
this._firestore = firestore;
|
||||
SharedEventEmitter.addListener(
|
||||
getAppEventName(this._firestore, 'firestore_transaction_event'),
|
||||
this._handleTransactionEvent.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* INTERNAL API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add a new transaction and start it natively.
|
||||
* @param updateFunction
|
||||
*/
|
||||
_add(
|
||||
updateFunction: (transaction: Transaction) => Promise<any>
|
||||
): Promise<any> {
|
||||
const id = generateTransactionId();
|
||||
const meta = {
|
||||
id,
|
||||
reject: null,
|
||||
resolve: null,
|
||||
updateFunction,
|
||||
stack: new Error().stack
|
||||
.split('\n')
|
||||
.slice(4)
|
||||
.join('\n'),
|
||||
};
|
||||
|
||||
meta.transaction = new Transaction(this._firestore, meta);
|
||||
this._pending[id] = meta;
|
||||
|
||||
// deferred promise
|
||||
return new Promise((resolve, reject) => {
|
||||
getNativeModule(this._firestore).transactionBegin(id);
|
||||
meta.resolve = r => {
|
||||
resolve(r);
|
||||
this._remove(id);
|
||||
};
|
||||
meta.reject = e => {
|
||||
reject(e);
|
||||
this._remove(id);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a local instance of a transaction meta
|
||||
*
|
||||
* @param id
|
||||
* @private
|
||||
*/
|
||||
_remove(id) {
|
||||
// todo confirm pending arg no longer needed
|
||||
getNativeModule(this._firestore).transactionDispose(id);
|
||||
// TODO may need delaying to next event loop
|
||||
delete this._pending[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* EVENTS
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction events and distributes to correct
|
||||
* internal handler by event.type
|
||||
*
|
||||
* @param event
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_handleTransactionEvent(event: TransactionEvent) {
|
||||
switch (event.type) {
|
||||
case 'update':
|
||||
return this._handleUpdate(event);
|
||||
case 'error':
|
||||
return this._handleError(event);
|
||||
case 'complete':
|
||||
return this._handleComplete(event);
|
||||
default:
|
||||
getLogger(this._firestore).warn(
|
||||
`Unknown transaction event type: '${event.type}'`,
|
||||
event
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction update events
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
async _handleUpdate(event: TransactionEvent) {
|
||||
const { id } = event;
|
||||
// abort if no longer exists js side
|
||||
if (!this._pending[id]) return this._remove(id);
|
||||
|
||||
const { updateFunction, transaction, reject } = this._pending[id];
|
||||
|
||||
// clear any saved state from previous transaction runs
|
||||
transaction._prepare();
|
||||
|
||||
let finalError;
|
||||
let updateFailed;
|
||||
let pendingResult;
|
||||
|
||||
// run the users custom update functionality
|
||||
try {
|
||||
const possiblePromise = updateFunction(transaction);
|
||||
|
||||
// validate user has returned a promise in their update function
|
||||
// TODO must it actually return a promise? Can't find any usages of it without one...
|
||||
if (!possiblePromise || !possiblePromise.then) {
|
||||
finalError = new Error(
|
||||
'Update function for `firestore.runTransaction(updateFunction)` must return a Promise.'
|
||||
);
|
||||
} else {
|
||||
pendingResult = await possiblePromise;
|
||||
}
|
||||
} catch (exception) {
|
||||
// exception can still be falsey if user `Promise.reject();` 's with no args
|
||||
// so we track the exception with a updateFailed boolean to ensure no fall-through
|
||||
updateFailed = true;
|
||||
finalError = exception;
|
||||
}
|
||||
|
||||
// reject the final promise and remove from native
|
||||
// update is failed when either the users updateFunction
|
||||
// throws an error or rejects a promise
|
||||
if (updateFailed) {
|
||||
return reject(finalError);
|
||||
}
|
||||
|
||||
// capture the resolved result as we'll need this
|
||||
// to resolve the runTransaction() promise when
|
||||
// native emits that the transaction is final
|
||||
transaction._pendingResult = pendingResult;
|
||||
|
||||
// send the buffered update/set/delete commands for native to process
|
||||
return getNativeModule(this._firestore).transactionApplyBuffer(
|
||||
id,
|
||||
transaction._commandBuffer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction error events
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleError(event: TransactionEvent) {
|
||||
const { id, error } = event;
|
||||
const meta = this._pending[id];
|
||||
|
||||
if (meta) {
|
||||
const { code, message } = error;
|
||||
// build a JS error and replace its stack
|
||||
// with the captured one at start of transaction
|
||||
// so it's actually relevant to the user
|
||||
const errorWithStack = new Error(message);
|
||||
errorWithStack.code = code;
|
||||
errorWithStack.stack = meta.stack;
|
||||
|
||||
meta.reject(errorWithStack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming native transaction complete events
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_handleComplete(event: TransactionEvent) {
|
||||
const { id } = event;
|
||||
const meta = this._pending[id];
|
||||
|
||||
if (meta) {
|
||||
const pendingResult = meta.transaction._pendingResult;
|
||||
meta.resolve(pendingResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ import FieldValue from './FieldValue';
|
|||
import GeoPoint from './GeoPoint';
|
||||
import Path from './Path';
|
||||
import WriteBatch from './WriteBatch';
|
||||
import TransactionHandler from './TransactionHandler';
|
||||
import Transaction from './Transaction';
|
||||
import INTERNALS from '../../utils/internals';
|
||||
|
||||
import type DocumentSnapshot from './DocumentSnapshot';
|
||||
|
@ -36,8 +38,9 @@ type DocumentSyncEvent = {
|
|||
};
|
||||
|
||||
const NATIVE_EVENTS = [
|
||||
'firestore_collection_sync_event',
|
||||
'firestore_transaction_event',
|
||||
'firestore_document_sync_event',
|
||||
'firestore_collection_sync_event',
|
||||
];
|
||||
|
||||
export const MODULE_NAME = 'RNFirebaseFirestore';
|
||||
|
@ -48,6 +51,7 @@ export const NAMESPACE = 'firestore';
|
|||
*/
|
||||
export default class Firestore extends ModuleBase {
|
||||
_referencePath: Path;
|
||||
_transactionHandler: TransactionHandler;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app, {
|
||||
|
@ -56,7 +60,9 @@ export default class Firestore extends ModuleBase {
|
|||
multiApp: true,
|
||||
namespace: NAMESPACE,
|
||||
});
|
||||
|
||||
this._referencePath = new Path([]);
|
||||
this._transactionHandler = new TransactionHandler(this);
|
||||
|
||||
SharedEventEmitter.addListener(
|
||||
// sub to internal native event - this fans out to
|
||||
|
@ -73,11 +79,23 @@ export default class Firestore extends ModuleBase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* PUBLIC API
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a write batch, used for performing multiple writes as a single atomic operation.
|
||||
*
|
||||
* @returns {WriteBatch}
|
||||
*/
|
||||
batch(): WriteBatch {
|
||||
return new WriteBatch(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a CollectionReference instance that refers to the collection at the specified path.
|
||||
*
|
||||
* @param collectionPath
|
||||
* @returns {CollectionReference}
|
||||
|
@ -92,6 +110,7 @@ export default class Firestore extends ModuleBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets a DocumentReference instance that refers to the document at the specified path.
|
||||
*
|
||||
* @param documentPath
|
||||
* @returns {DocumentReference}
|
||||
|
@ -105,13 +124,27 @@ export default class Firestore extends ModuleBase {
|
|||
return new DocumentReference(this, path);
|
||||
}
|
||||
|
||||
enablePersistence(): Promise<void> {
|
||||
throw new Error('Persistence is enabled by default on the Firestore SDKs');
|
||||
/**
|
||||
* Executes the given updateFunction and then attempts to commit the
|
||||
* changes applied within the transaction. If any document read within
|
||||
* the transaction has changed, Cloud Firestore retries the updateFunction.
|
||||
*
|
||||
* If it fails to commit after 5 attempts, the transaction fails.
|
||||
*
|
||||
* @param updateFunction
|
||||
* @returns {void|Promise<any>}
|
||||
*/
|
||||
runTransaction(
|
||||
updateFunction: (transaction: Transaction) => Promise<any>
|
||||
): Promise<any> {
|
||||
return this._transactionHandler._add(updateFunction);
|
||||
}
|
||||
|
||||
runTransaction(): Promise<any> {
|
||||
throw new Error('firebase.firestore().runTransaction() coming soon');
|
||||
}
|
||||
/**
|
||||
* -------------
|
||||
* UNSUPPORTED
|
||||
* -------------
|
||||
*/
|
||||
|
||||
setLogLevel(): void {
|
||||
throw new Error(
|
||||
|
@ -122,12 +155,47 @@ export default class Firestore extends ModuleBase {
|
|||
);
|
||||
}
|
||||
|
||||
enableNetwork(): void {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'firestore',
|
||||
'enableNetwork'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
disableNetwork(): void {
|
||||
throw new Error(
|
||||
INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD(
|
||||
'firestore',
|
||||
'disableNetwork'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* MISC
|
||||
* -------------
|
||||
*/
|
||||
|
||||
enablePersistence(): Promise<void> {
|
||||
throw new Error('Persistence is enabled by default on the Firestore SDKs');
|
||||
}
|
||||
|
||||
settings(): void {
|
||||
throw new Error('firebase.firestore().settings() coming soon');
|
||||
}
|
||||
|
||||
/**
|
||||
* -------------
|
||||
* INTERNALS
|
||||
* -------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal collection sync listener
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
|
@ -147,6 +215,7 @@ export default class Firestore extends ModuleBase {
|
|||
|
||||
/**
|
||||
* Internal document sync listener
|
||||
*
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
|
|
|
@ -49,7 +49,7 @@ export const buildNativeArray = (array: Object[]): NativeTypeMap[] => {
|
|||
|
||||
export const buildTypeMap = (value: any): NativeTypeMap | null => {
|
||||
const type = typeOf(value);
|
||||
if (value === null || value === undefined) {
|
||||
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||
return {
|
||||
type: 'null',
|
||||
value: null,
|
||||
|
|
|
@ -92,7 +92,7 @@ export default class StorageTask {
|
|||
if (!isFunction(f)) return null;
|
||||
return error => {
|
||||
const _error = new Error(error.message);
|
||||
// $FlowFixMe
|
||||
// $FlowExpectedError
|
||||
_error.code = error.code;
|
||||
return f && f(_error);
|
||||
};
|
||||
|
|
|
@ -32,13 +32,14 @@ export default {
|
|||
},
|
||||
|
||||
apps(): Array<App> {
|
||||
// $FlowBug: Object.values always returns mixed type: https://github.com/facebook/flow/issues/2221
|
||||
// $FlowExpectedError: Object.values always returns mixed type: https://github.com/facebook/flow/issues/2221
|
||||
return Object.values(APPS);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param statics
|
||||
* @param app
|
||||
* @param namespace
|
||||
* @param InstanceClass
|
||||
* @return {function()}
|
||||
* @private
|
||||
|
@ -152,8 +153,9 @@ export default {
|
|||
|
||||
/**
|
||||
*
|
||||
* @param namespace
|
||||
* @param statics
|
||||
* @param InstanceClass
|
||||
* @param moduleName
|
||||
* @return {function(App=)}
|
||||
*/
|
||||
moduleAndStatics<M: FirebaseModule, S: FirebaseStatics>(
|
||||
|
@ -174,7 +176,7 @@ export default {
|
|||
if (namespace === 'crashlytics') {
|
||||
return _app.fabric[namespace]();
|
||||
}
|
||||
// $FlowBug: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
// $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323
|
||||
const module = _app[namespace];
|
||||
return module();
|
||||
};
|
||||
|
|
|
@ -201,7 +201,7 @@ class EventEmitter {
|
|||
* }); // removes the listener if already registered
|
||||
*
|
||||
*/
|
||||
removeListener(eventType: String, listener) {
|
||||
removeListener(eventType: string, listener) {
|
||||
const subscriptions: ?[EmitterSubscription] = (this._subscriber.getSubscriptionsForType(eventType): any);
|
||||
if (subscriptions) {
|
||||
for (let i = 0, l = subscriptions.length; i < l; i++) {
|
||||
|
|
|
@ -170,12 +170,6 @@ export function tryJSONStringify(data: mixed): string | null {
|
|||
}
|
||||
}
|
||||
|
||||
export const windowOrGlobal =
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
(typeof self === 'object' && self.self === self && self) ||
|
||||
(typeof global === 'object' && global.global === global && global) ||
|
||||
this;
|
||||
|
||||
/**
|
||||
* No operation func
|
||||
*/
|
||||
|
@ -282,7 +276,7 @@ export function typeOf(value: any): string {
|
|||
// * @param string
|
||||
// * @return {string}
|
||||
// */
|
||||
// export function capitalizeFirstLetter(string: String) {
|
||||
// export function capitalizeFirstLetter(string: string) {
|
||||
// return `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
|
||||
// }
|
||||
|
||||
|
|
|
@ -21,35 +21,35 @@ const GRADLE_DEPS = {
|
|||
};
|
||||
|
||||
const PLAY_SERVICES_CODES = {
|
||||
// $FlowBug: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
// $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
1: {
|
||||
code: 'SERVICE_MISSING',
|
||||
message: 'Google Play services is missing on this device.',
|
||||
},
|
||||
// $FlowBug: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
// $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
2: {
|
||||
code: 'SERVICE_VERSION_UPDATE_REQUIRED',
|
||||
message:
|
||||
'The installed version of Google Play services on this device is out of date.',
|
||||
},
|
||||
// $FlowBug: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
// $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
3: {
|
||||
code: 'SERVICE_DISABLED',
|
||||
message:
|
||||
'The installed version of Google Play services has been disabled on this device.',
|
||||
},
|
||||
// $FlowBug: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
// $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
9: {
|
||||
code: 'SERVICE_INVALID',
|
||||
message:
|
||||
'The version of the Google Play services installed on this device is not authentic.',
|
||||
},
|
||||
// $FlowBug: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
// $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
18: {
|
||||
code: 'SERVICE_UPDATING',
|
||||
message: 'Google Play services is currently being updated on this device.',
|
||||
},
|
||||
// $FlowBug: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
// $FlowExpectedError: Doesn't like numerical object keys: https://github.com/facebook/flow/issues/380
|
||||
19: {
|
||||
code: 'SERVICE_MISSING_PERMISSION',
|
||||
message:
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
/*
|
||||
* @flow
|
||||
*/
|
||||
import { windowOrGlobal } from './';
|
||||
|
||||
import INTERNALS from './internals';
|
||||
import type ModuleBase from './ModuleBase';
|
||||
|
||||
(base => {
|
||||
window = base || window;
|
||||
// $FlowFixMe: Why are we using localStorage at all?
|
||||
if (!window.localStorage) window.localStorage = {};
|
||||
})(windowOrGlobal);
|
||||
|
||||
// clean up time
|
||||
|
||||
const NATIVE_LOGGERS: { [string]: Object } = {};
|
||||
|
||||
const getModuleKey = (module: ModuleBase): string =>
|
||||
|
@ -23,22 +15,33 @@ export const getLogger = (module: ModuleBase) => {
|
|||
return NATIVE_LOGGERS[key];
|
||||
};
|
||||
|
||||
export const LEVELS = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
};
|
||||
|
||||
export const initialiseLogger = (module: ModuleBase, logNamespace: string) => {
|
||||
const key = getModuleKey(module);
|
||||
if (!NATIVE_LOGGERS[key]) {
|
||||
// eslint-disable-next-line global-require
|
||||
NATIVE_LOGGERS[key] = require('bows')(`🔥 ${logNamespace.toUpperCase()}`);
|
||||
const prefix = `🔥 ${logNamespace.toUpperCase()}`;
|
||||
NATIVE_LOGGERS[key] = {
|
||||
debug(...args) {
|
||||
if (__DEV__ && LEVELS.debug >= LEVELS[INTERNALS.OPTIONS.logLevel])
|
||||
console.log(...[prefix, ...args]);
|
||||
},
|
||||
info(...args) {
|
||||
if (__DEV__ && LEVELS.info >= LEVELS[INTERNALS.OPTIONS.logLevel])
|
||||
console.log(...[prefix, ...args]);
|
||||
},
|
||||
warn(...args) {
|
||||
if (__DEV__ && LEVELS.warn >= LEVELS[INTERNALS.OPTIONS.logLevel])
|
||||
console.warn(...args);
|
||||
},
|
||||
error(...args) {
|
||||
console.error(...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default class Log {
|
||||
static createLogger(namespace: string) {
|
||||
// eslint-disable-next-line global-require
|
||||
return require('bows')(namespace);
|
||||
}
|
||||
|
||||
static setLevel(booleanOrDebugString: boolean | string) {
|
||||
window.localStorage.debug = booleanOrDebugString;
|
||||
window.localStorage.debugColors = !!booleanOrDebugString;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-native-firebase",
|
||||
"version": "3.2.5",
|
||||
"version": "3.3.0",
|
||||
"author": "Invertase <contact@invertase.io> (http://invertase.io)",
|
||||
"description": "A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Messaging (FCM), Remote Config, Storage and Performance.",
|
||||
"main": "dist/index.js",
|
||||
|
@ -35,7 +35,7 @@
|
|||
"unmockedModulePathPatterns": [
|
||||
"./node_modules/react",
|
||||
"./node_modules/react-native",
|
||||
"./node_modules/react-native-mock",
|
||||
"./node_modues/react-native-mock",
|
||||
"./node_modules/react-addons-test-utils"
|
||||
]
|
||||
},
|
||||
|
@ -79,39 +79,38 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"babel-jest": "^14.1.0",
|
||||
"babel-preset-react-native": "^1.9.0",
|
||||
"babel-preset-react-native-syntax": "^1.0.0",
|
||||
"debug": "^2.2.0",
|
||||
"debug": "^2.6.9",
|
||||
"enzyme": "^2.4.1",
|
||||
"eslint": "^4.11.0",
|
||||
"eslint": "^4.18.1",
|
||||
"eslint-config-airbnb": "^16.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-flowtype": "^2.39.1",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||
"eslint-plugin-prettier": "^2.5.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-flowtype": "^2.46.1",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"flow-bin": "^0.61.0",
|
||||
"flow-copy-source": "^1.2.1",
|
||||
"flow-copy-source": "^1.3.0",
|
||||
"genversion": "^2.0.1",
|
||||
"husky": "^0.14.3",
|
||||
"lint-staged": "^6.0.1",
|
||||
"lint-staged": "^6.1.1",
|
||||
"prettier": "1.10.2",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react-native": "^0.52.3",
|
||||
"rimraf": "^2.6.2",
|
||||
"shelljs": "^0.7.8",
|
||||
"typescript": "^2.6.2",
|
||||
"typescript": "^2.7.2",
|
||||
"wml": "0.0.82"
|
||||
},
|
||||
"dependencies": {
|
||||
"bows": "^1.6.0",
|
||||
"opencollective": "^1.0.3",
|
||||
"postinstall-build": "^5.0.1",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.1"
|
||||
},
|
||||
"rnpm": {
|
||||
"android": {
|
||||
|
|
|
@ -164,7 +164,7 @@ PODS:
|
|||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNFirebase (3.2.5):
|
||||
- RNFirebase (3.3.0):
|
||||
- React
|
||||
- yoga (0.52.0.React)
|
||||
|
||||
|
@ -228,7 +228,7 @@ SPEC CHECKSUMS:
|
|||
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
|
||||
Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03
|
||||
React: 61a6bdf17a9ff16875c230e6ff278d9de274e16c
|
||||
RNFirebase: e3448c730955d51d06dee59a265011536abdd7c4
|
||||
RNFirebase: 056b672391f3e7b572d8ef221c84603970e0c114
|
||||
yoga: 646606bf554d54a16711f35596178522fbc00480
|
||||
|
||||
PODFILE CHECKSUM: 67c98bcb203cb992da590bcab6f690f727653ca5
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-ReactNativeFirebaseDemo/Pods-ReactNativeFirebaseDemo-resources.sh",
|
||||
$PODS_CONFIGURATION_BUILD_DIR/gRPC/gRPCCertificates.bundle,
|
||||
"$PODS_CONFIGURATION_BUILD_DIR/gRPC/gRPCCertificates.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -28,26 +28,26 @@
|
|||
"bows": "^1.6.0",
|
||||
"cuid": "^1.3.8",
|
||||
"deeps": "^1.4.4",
|
||||
"firebase": "^4.8.2",
|
||||
"js-beautify": "^1.6.11",
|
||||
"firebase": "^4.10.1",
|
||||
"js-beautify": "^1.7.5",
|
||||
"lodash.groupby": "^4.6.0",
|
||||
"lodash.some": "^4.6.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"query-string": "^5.0.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"query-string": "^5.1.0",
|
||||
"react": "^16.2.0",
|
||||
"react-native": "^0.52.0",
|
||||
"react-native": "^0.52.3",
|
||||
"react-native-vector-icons": "^4.5.0",
|
||||
"react-navigation": "^1.0.0-beta.9",
|
||||
"react-redux": "^5.0.3",
|
||||
"react-navigation": "^1.2.1",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-test-renderer": "16.2.0",
|
||||
"redux": "^3.6.0",
|
||||
"redux-logger": "^2.8.2",
|
||||
"redux-persist": "^4.4.2",
|
||||
"redux-persist": "^4.10.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"should": "^11.2.0",
|
||||
"should-sinon": "^0.0.5",
|
||||
"sinon": "^3.2.1",
|
||||
"url-parse": "^1.1.9"
|
||||
"url-parse": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.24.0",
|
||||
|
@ -59,8 +59,8 @@
|
|||
"colors": "^1.1.2",
|
||||
"eslint": "^3.16.1",
|
||||
"eslint-config-airbnb": "^14.1.0",
|
||||
"eslint-plugin-flowtype": "^2.30.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-flowtype": "^2.46.1",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^4.0.0",
|
||||
"eslint-plugin-react": "^6.10.0",
|
||||
"jest": "19.0.2",
|
||||
|
|
|
@ -4,8 +4,12 @@ import firebase from 'firebase';
|
|||
import RNfirebase from './../firebase';
|
||||
import DatabaseContents from './tests/support/DatabaseContents';
|
||||
|
||||
RNfirebase.database.enableLogging(true);
|
||||
RNfirebase.firestore.enableLogging(true);
|
||||
// RNfirebase.database.enableLogging(true);
|
||||
// RNfirebase.firestore.enableLogging(true);
|
||||
|
||||
// RNfirebase.utils().logLevel = 'debug';
|
||||
// RNfirebase.utils().logLevel = 'info';
|
||||
RNfirebase.utils().logLevel = 'warn'; // default
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
|
@ -112,18 +116,22 @@ console.log('RNApps -->', RNfirebase.apps);
|
|||
// no need for ready checks
|
||||
instances.native
|
||||
.auth()
|
||||
.signInAnonymously()
|
||||
.then(user => {
|
||||
console.log('defaultApp user ->', user.toJSON());
|
||||
.signInAnonymouslyAndRetrieveData()
|
||||
.then(credential => {
|
||||
if (credential) {
|
||||
console.log('anotherApp credential ->', credential.user.toJSON());
|
||||
}
|
||||
});
|
||||
|
||||
// dynamically initialized apps need a ready check
|
||||
instances.another.onReady().then(app => {
|
||||
app
|
||||
.auth()
|
||||
.signInAnonymously()
|
||||
.then(user => {
|
||||
console.log('anotherApp user ->', user.toJSON());
|
||||
.signInAnonymouslyAndRetrieveData()
|
||||
.then(credential => {
|
||||
if (credential) {
|
||||
console.log('anotherApp credential ->', credential.user.toJSON());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -16,6 +16,24 @@ export default function addTests({ describe, it, firebase }) {
|
|||
resolve();
|
||||
}));
|
||||
|
||||
it('logEvent should error if name is not a string', () => {
|
||||
(() => {
|
||||
firebase.native.analytics().logEvent(123456);
|
||||
}).should.throw(
|
||||
`analytics.logEvent(): First argument 'name' is required and must be a string value.`
|
||||
);
|
||||
});
|
||||
|
||||
it('logEvent should error if params is not an object', () => {
|
||||
(() => {
|
||||
firebase.native
|
||||
.analytics()
|
||||
.logEvent('test_event', 'this should be an object');
|
||||
}).should.throw(
|
||||
`analytics.logEvent(): Second optional argument 'params' must be an object if provided.`
|
||||
);
|
||||
});
|
||||
|
||||
it('setAnalyticsCollectionEnabled: it should run without error', () =>
|
||||
new Promise(resolve => {
|
||||
firebase.native.analytics().setAnalyticsCollectionEnabled(true);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,9 +2,13 @@ import firebase from '../../firebase';
|
|||
import TestSuite from '../../../lib/TestSuite';
|
||||
|
||||
import authTests from './authTests';
|
||||
import providerTests from './providerTests';
|
||||
import userTests from './userTests';
|
||||
|
||||
const suite = new TestSuite('Auth', 'firebase.auth()', firebase);
|
||||
|
||||
suite.addTests(authTests);
|
||||
suite.addTests(providerTests);
|
||||
suite.addTests(userTests);
|
||||
|
||||
export default suite;
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
export default (providerTests = ({ context, describe, it, firebase }) => {
|
||||
describe('EmailAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.EmailAuthProvider()).should.throw(
|
||||
'`new EmailAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const email = 'email@email.com';
|
||||
const password = 'password';
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
password
|
||||
);
|
||||
credential.providerId.should.equal('password');
|
||||
credential.token.should.equal(email);
|
||||
credential.secret.should.equal(password);
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return password', () => {
|
||||
firebase.native.auth.EmailAuthProvider.PROVIDER_ID.should.equal(
|
||||
'password'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FacebookAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.FacebookAuthProvider()).should.throw(
|
||||
'`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const token = '123456';
|
||||
const credential = firebase.native.auth.FacebookAuthProvider.credential(
|
||||
token
|
||||
);
|
||||
credential.providerId.should.equal('facebook.com');
|
||||
credential.token.should.equal(token);
|
||||
credential.secret.should.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return facebook.com', () => {
|
||||
firebase.native.auth.FacebookAuthProvider.PROVIDER_ID.should.equal(
|
||||
'facebook.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GithubAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.GithubAuthProvider()).should.throw(
|
||||
'`new GithubAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const token = '123456';
|
||||
const credential = firebase.native.auth.GithubAuthProvider.credential(
|
||||
token
|
||||
);
|
||||
credential.providerId.should.equal('github.com');
|
||||
credential.token.should.equal(token);
|
||||
credential.secret.should.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return github.com', () => {
|
||||
firebase.native.auth.GithubAuthProvider.PROVIDER_ID.should.equal(
|
||||
'github.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GoogleAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.GoogleAuthProvider()).should.throw(
|
||||
'`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const token = '123456';
|
||||
const secret = '654321';
|
||||
const credential = firebase.native.auth.GoogleAuthProvider.credential(
|
||||
token,
|
||||
secret
|
||||
);
|
||||
credential.providerId.should.equal('google.com');
|
||||
credential.token.should.equal(token);
|
||||
credential.secret.should.equal(secret);
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return google.com', () => {
|
||||
firebase.native.auth.GoogleAuthProvider.PROVIDER_ID.should.equal(
|
||||
'google.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.OAuthProvider()).should.throw(
|
||||
'`new OAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const idToken = '123456';
|
||||
const accessToken = '654321';
|
||||
const credential = firebase.native.auth.OAuthProvider.credential(
|
||||
idToken,
|
||||
accessToken
|
||||
);
|
||||
credential.providerId.should.equal('oauth');
|
||||
credential.token.should.equal(idToken);
|
||||
credential.secret.should.equal(accessToken);
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return oauth', () => {
|
||||
firebase.native.auth.OAuthProvider.PROVIDER_ID.should.equal('oauth');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PhoneAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.PhoneAuthProvider()).should.throw(
|
||||
'`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const verificationId = '123456';
|
||||
const code = '654321';
|
||||
const credential = firebase.native.auth.PhoneAuthProvider.credential(
|
||||
verificationId,
|
||||
code
|
||||
);
|
||||
credential.providerId.should.equal('phone');
|
||||
credential.token.should.equal(verificationId);
|
||||
credential.secret.should.equal(code);
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return phone', () => {
|
||||
firebase.native.auth.PhoneAuthProvider.PROVIDER_ID.should.equal(
|
||||
'phone'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('TwitterAuthProvider', () => {
|
||||
context('constructor', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => new firebase.native.auth.TwitterAuthProvider()).should.throw(
|
||||
'`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('credential', () => {
|
||||
it('should return a credential object', () => {
|
||||
const token = '123456';
|
||||
const secret = '654321';
|
||||
const credential = firebase.native.auth.TwitterAuthProvider.credential(
|
||||
token,
|
||||
secret
|
||||
);
|
||||
credential.providerId.should.equal('twitter.com');
|
||||
credential.token.should.equal(token);
|
||||
credential.secret.should.equal(secret);
|
||||
});
|
||||
});
|
||||
|
||||
context('PROVIDER_ID', () => {
|
||||
it('should return twitter.com', () => {
|
||||
firebase.native.auth.TwitterAuthProvider.PROVIDER_ID.should.equal(
|
||||
'twitter.com'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,504 @@
|
|||
const randomString = (length, chars) => {
|
||||
let mask = '';
|
||||
if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
|
||||
if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
if (chars.indexOf('#') > -1) mask += '0123456789';
|
||||
if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\';
|
||||
let result = '';
|
||||
for (let i = length; i > 0; --i) {
|
||||
result += mask[Math.round(Math.random() * (mask.length - 1))];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export default (userTests = ({ context, describe, it, firebase }) => {
|
||||
describe('User', () => {
|
||||
context('getIdToken()', () => {
|
||||
it('should return a token', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
const newUser = await firebase.native
|
||||
.auth()
|
||||
.createUserWithEmailAndPassword(email, pass);
|
||||
|
||||
// Test
|
||||
const token = await newUser.getIdToken();
|
||||
|
||||
// Assertions
|
||||
token.should.be.a.String();
|
||||
token.length.should.be.greaterThan(24);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('getToken()', () => {
|
||||
it('should return a token', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
const newUser = await firebase.native
|
||||
.auth()
|
||||
.createUserWithEmailAndPassword(email, pass);
|
||||
|
||||
// Test
|
||||
const token = await newUser.getToken();
|
||||
|
||||
// Assertions
|
||||
token.should.be.a.String();
|
||||
token.length.should.be.greaterThan(24);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('linkWithCredential()', () => {
|
||||
it('should link anonymous account <-> email account', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
|
||||
// Test
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
|
||||
const linkedUser = await currentUser.linkWithCredential(credential);
|
||||
|
||||
// Assertions
|
||||
linkedUser.should.be.an.Object();
|
||||
linkedUser.should.equal(firebase.native.auth().currentUser);
|
||||
linkedUser.email.toLowerCase().should.equal(email.toLowerCase());
|
||||
linkedUser.isAnonymous.should.equal(false);
|
||||
linkedUser.providerId.should.equal('firebase');
|
||||
linkedUser.providerData.should.be.an.Array();
|
||||
linkedUser.providerData.length.should.equal(1);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
|
||||
it('should error on link anon <-> email if email already exists', async () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
|
||||
// Test
|
||||
try {
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
await currentUser.linkWithCredential(credential);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().signOut();
|
||||
|
||||
// Reject
|
||||
Promise.reject(new Error('Did not error on link'));
|
||||
} catch (error) {
|
||||
// Assertions
|
||||
error.code.should.equal('auth/email-already-in-use');
|
||||
error.message.should.equal(
|
||||
'The email address is already in use by another account.'
|
||||
);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('linkAndRetrieveDataWithCredential()', () => {
|
||||
it('should link anonymous account <-> email account', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
|
||||
// Test
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
|
||||
const linkedUserCredential = await currentUser.linkAndRetrieveDataWithCredential(
|
||||
credential
|
||||
);
|
||||
|
||||
// Assertions
|
||||
const linkedUser = linkedUserCredential.user;
|
||||
linkedUser.should.be.an.Object();
|
||||
linkedUser.should.equal(firebase.native.auth().currentUser);
|
||||
linkedUser.email.toLowerCase().should.equal(email.toLowerCase());
|
||||
linkedUser.isAnonymous.should.equal(false);
|
||||
linkedUser.providerId.should.equal('firebase');
|
||||
linkedUser.providerData.should.be.an.Array();
|
||||
linkedUser.providerData.length.should.equal(1);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
|
||||
it('should error on link anon <-> email if email already exists', async () => {
|
||||
const email = 'test@test.com';
|
||||
const pass = 'test1234';
|
||||
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
|
||||
// Test
|
||||
try {
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
await currentUser.linkAndRetrieveDataWithCredential(credential);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().signOut();
|
||||
|
||||
// Reject
|
||||
Promise.reject(new Error('Did not error on link'));
|
||||
} catch (error) {
|
||||
// Assertions
|
||||
error.code.should.equal('auth/email-already-in-use');
|
||||
error.message.should.equal(
|
||||
'The email address is already in use by another account.'
|
||||
);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('reauthenticateWithCredential()', () => {
|
||||
it('should reauthenticate correctly', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
await firebase.native
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
|
||||
|
||||
// Test
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
await firebase.native
|
||||
.auth()
|
||||
.currentUser.reauthenticateWithCredential(credential);
|
||||
|
||||
// Assertions
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
currentUser.email.should.equal(email.toLowerCase());
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('reauthenticateAndRetrieveDataWithCredential()', () => {
|
||||
it('should reauthenticate correctly', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
await firebase.native
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
|
||||
|
||||
// Test
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
await firebase.native
|
||||
.auth()
|
||||
.currentUser.reauthenticateAndRetrieveDataWithCredential(credential);
|
||||
|
||||
// Assertions
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
currentUser.email.should.equal(email.toLowerCase());
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('reload()', () => {
|
||||
it('should not error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
|
||||
try {
|
||||
await firebase.native.auth().currentUser.reload();
|
||||
await firebase.native.auth().signOut();
|
||||
} catch (error) {
|
||||
// Reject
|
||||
await firebase.native.auth().signOut();
|
||||
Promise.reject(new Error('reload() caused an error', error));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('sendEmailVerification()', () => {
|
||||
it('should not error', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
await firebase.native
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
|
||||
|
||||
try {
|
||||
await firebase.native.auth().currentUser.sendEmailVerification();
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
} catch (error) {
|
||||
// Reject
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
Promise.reject(
|
||||
new Error('sendEmailVerification() caused an error', error)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('unlink()', () => {
|
||||
it('should unlink the email address', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
const currentUser = firebase.native.auth().currentUser;
|
||||
|
||||
const credential = firebase.native.auth.EmailAuthProvider.credential(
|
||||
email,
|
||||
pass
|
||||
);
|
||||
await currentUser.linkAndRetrieveDataWithCredential(credential);
|
||||
|
||||
// Test
|
||||
await currentUser.unlink(
|
||||
firebase.native.auth.EmailAuthProvider.PROVIDER_ID
|
||||
);
|
||||
|
||||
// Assertions
|
||||
const unlinkedUser = firebase.native.auth().currentUser;
|
||||
unlinkedUser.providerData.should.be.an.Array();
|
||||
unlinkedUser.providerData.length.should.equal(0);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('updateEmail()', () => {
|
||||
it('should update the email address', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const random2 = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const email2 = `${random2}@${random2}.com`;
|
||||
const pass = random;
|
||||
|
||||
// Setup
|
||||
await firebase.native
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
|
||||
firebase.native
|
||||
.auth()
|
||||
.currentUser.email.toLowerCase()
|
||||
.should.equal(email.toLowerCase());
|
||||
|
||||
// Update user email
|
||||
await firebase.native.auth().currentUser.updateEmail(email2);
|
||||
|
||||
// Assertions
|
||||
firebase.native
|
||||
.auth()
|
||||
.currentUser.email.toLowerCase()
|
||||
.should.equal(email2.toLowerCase());
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('updatePassword()', () => {
|
||||
it('should update the password', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const random2 = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
const pass2 = random2;
|
||||
|
||||
// Setup
|
||||
await firebase.native
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
|
||||
|
||||
// Update user password
|
||||
await firebase.native.auth().currentUser.updatePassword(pass2);
|
||||
|
||||
// Sign out
|
||||
await firebase.native.auth().signOut();
|
||||
|
||||
// Log in with the new password
|
||||
await firebase.native
|
||||
.auth()
|
||||
.signInAndRetrieveDataWithEmailAndPassword(email, pass2);
|
||||
|
||||
// Assertions
|
||||
firebase.native.auth().currentUser.should.be.an.Object();
|
||||
firebase.native
|
||||
.auth()
|
||||
.currentUser.email.should.equal(email.toLowerCase());
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('updateProfile()', () => {
|
||||
it('should update the profile', async () => {
|
||||
const random = randomString(12, '#aA');
|
||||
const email = `${random}@${random}.com`;
|
||||
const pass = random;
|
||||
const displayName = random;
|
||||
const photoURL = `http://${random}.com/${random}.jpg`;
|
||||
|
||||
// Setup
|
||||
await firebase.native
|
||||
.auth()
|
||||
.createUserAndRetrieveDataWithEmailAndPassword(email, pass);
|
||||
|
||||
// Update user profile
|
||||
await firebase.native.auth().currentUser.updateProfile({
|
||||
displayName,
|
||||
photoURL,
|
||||
});
|
||||
|
||||
// Assertions
|
||||
const user = firebase.native.auth().currentUser;
|
||||
user.should.be.an.Object();
|
||||
user.email.should.equal(email.toLowerCase());
|
||||
user.displayName.should.equal(displayName);
|
||||
user.photoURL.should.equal(photoURL);
|
||||
|
||||
// Clean up
|
||||
await firebase.native.auth().currentUser.delete();
|
||||
});
|
||||
});
|
||||
|
||||
context('linkWithPhoneNumber()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.linkWithPhoneNumber();
|
||||
}).should.throw(
|
||||
'User.linkWithPhoneNumber() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('linkWithPopup()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.linkWithPopup();
|
||||
}).should.throw(
|
||||
'User.linkWithPopup() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('linkWithRedirect()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.linkWithRedirect();
|
||||
}).should.throw(
|
||||
'User.linkWithRedirect() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('reauthenticateWithPhoneNumber()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.reauthenticateWithPhoneNumber();
|
||||
}).should.throw(
|
||||
'User.reauthenticateWithPhoneNumber() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('reauthenticateWithPopup()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.reauthenticateWithPopup();
|
||||
}).should.throw(
|
||||
'User.reauthenticateWithPopup() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('reauthenticateWithRedirect()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.reauthenticateWithRedirect();
|
||||
}).should.throw(
|
||||
'User.reauthenticateWithRedirect() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('updatePhoneNumber()', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => {
|
||||
firebase.native.auth().currentUser.updatePhoneNumber();
|
||||
}).should.throw(
|
||||
'User.updatePhoneNumber() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
|
||||
context('refreshToken', () => {
|
||||
it('should throw an unsupported error', async () => {
|
||||
await firebase.native.auth().signInAnonymouslyAndRetrieveData();
|
||||
(() => firebase.native.auth().currentUser.refreshToken).should.throw(
|
||||
'User.refreshToken is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
await firebase.native.auth().signOut();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import { Platform } from 'react-native';
|
||||
import should from 'should';
|
||||
|
||||
import RNFirebase from './../../../firebase';
|
||||
import firebase from './../../../firebase';
|
||||
|
||||
const androidTestConfig = {
|
||||
// firebase android sdk completely ignores client id
|
||||
|
@ -33,52 +33,47 @@ function rand(from = 1, to = 9999) {
|
|||
}
|
||||
|
||||
function coreTests({ describe, it }) {
|
||||
describe('Core', () => {
|
||||
describe('Firebase', () => {
|
||||
it('it should create js apps for natively initialized apps', () => {
|
||||
should.equal(RNFirebase.app()._nativeInitialized, true);
|
||||
should.equal(firebase.app()._nativeInitialized, true);
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('natively initialized apps should have options available in js', () => {
|
||||
should.equal(
|
||||
RNFirebase.app().options.apiKey,
|
||||
firebase.app().options.apiKey,
|
||||
Platform.OS === 'ios' ? iosTestConfig.apiKey : androidTestConfig.apiKey
|
||||
);
|
||||
should.equal(
|
||||
RNFirebase.app().options.appId,
|
||||
firebase.app().options.appId,
|
||||
Platform.OS === 'ios' ? iosTestConfig.appId : androidTestConfig.appId
|
||||
);
|
||||
should.equal(
|
||||
RNFirebase.app().options.databaseURL,
|
||||
firebase.app().options.databaseURL,
|
||||
iosTestConfig.databaseURL
|
||||
);
|
||||
should.equal(
|
||||
RNFirebase.app().options.messagingSenderId,
|
||||
firebase.app().options.messagingSenderId,
|
||||
iosTestConfig.messagingSenderId
|
||||
);
|
||||
should.equal(RNFirebase.app().options.projectId, iosTestConfig.projectId);
|
||||
should.equal(firebase.app().options.projectId, iosTestConfig.projectId);
|
||||
should.equal(
|
||||
RNFirebase.app().options.storageBucket,
|
||||
firebase.app().options.storageBucket,
|
||||
iosTestConfig.storageBucket
|
||||
);
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('it should resolve onReady for natively initialized apps', () =>
|
||||
RNFirebase.app().onReady());
|
||||
|
||||
it('it should provide an array of apps', () => {
|
||||
should.equal(!!RNFirebase.apps.length, true);
|
||||
should.equal(RNFirebase.apps.includes(RNFirebase.app('[DEFAULT]')), true);
|
||||
return Promise.resolve();
|
||||
});
|
||||
firebase.app().onReady());
|
||||
|
||||
it('it should initialize dynamic apps', () => {
|
||||
const name = `testscoreapp${rand()}`;
|
||||
return RNFirebase.initializeApp(
|
||||
Platform.OS === 'ios' ? iosTestConfig : androidTestConfig,
|
||||
name
|
||||
)
|
||||
return firebase
|
||||
.initializeApp(
|
||||
Platform.OS === 'ios' ? iosTestConfig : androidTestConfig,
|
||||
name
|
||||
)
|
||||
.onReady()
|
||||
.then(newApp => {
|
||||
newApp.name.should.equal(name.toUpperCase());
|
||||
|
@ -90,6 +85,52 @@ function coreTests({ describe, it }) {
|
|||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('SDK_VERSION should return a string version', () => {
|
||||
firebase.SDK_VERSION.should.be.a.String();
|
||||
});
|
||||
});
|
||||
|
||||
describe('App', () => {
|
||||
it('apps should provide an array of apps', () => {
|
||||
should.equal(!!firebase.apps.length, true);
|
||||
should.equal(firebase.apps.includes(firebase.app('[DEFAULT]')), true);
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
it('delete is unsupported', () => {
|
||||
(() => {
|
||||
firebase.app().delete();
|
||||
}).should.throw(
|
||||
'app.delete() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
|
||||
it('extendApp should error if an object is not supplied', () => {
|
||||
(() => {
|
||||
firebase.app().extendApp('string');
|
||||
}).should.throw(
|
||||
"Missing required argument of type 'Object' for method 'extendApp()'."
|
||||
);
|
||||
});
|
||||
|
||||
it('extendApp should error if a protected property is supplied', () => {
|
||||
(() => {
|
||||
firebase.app().extendApp({
|
||||
database: {},
|
||||
});
|
||||
}).should.throw(
|
||||
"Property 'database' is protected and can not be overridden by extendApp."
|
||||
);
|
||||
});
|
||||
|
||||
it('extendApp should provide additional functionality', () => {
|
||||
const extension = {};
|
||||
firebase.app().extendApp({
|
||||
extension,
|
||||
});
|
||||
firebase.app().extension.should.equal(extension);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,15 @@ function collectionReferenceTests({
|
|||
}));
|
||||
});
|
||||
|
||||
context('parent', () => {
|
||||
it('should return parent document', () => {
|
||||
const collection = firebase.native
|
||||
.firestore()
|
||||
.collection('collection/document/subcollection');
|
||||
collection.parent.path.should.equal('collection/document');
|
||||
});
|
||||
});
|
||||
|
||||
context('add()', () => {
|
||||
it('should create Document', () =>
|
||||
firebase.native
|
||||
|
@ -51,6 +60,15 @@ function collectionReferenceTests({
|
|||
should.equal(docRef.path, 'collection-tests/doc');
|
||||
resolve();
|
||||
}));
|
||||
|
||||
it('should error when supplied an incorrect path', () => {
|
||||
(() => {
|
||||
firebase.native
|
||||
.firestore()
|
||||
.collection('collection')
|
||||
.doc('invalid/doc');
|
||||
}).should.throw('Argument "documentPath" must point to a document.');
|
||||
});
|
||||
});
|
||||
|
||||
context('get()', () => {
|
||||
|
@ -68,6 +86,47 @@ function collectionReferenceTests({
|
|||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('QuerySnapshot has correct properties', async () => {
|
||||
const snapshot = await firebase.native
|
||||
.firestore()
|
||||
.collection('collection-tests')
|
||||
.get();
|
||||
|
||||
snapshot.docChanges.should.be.an.Array();
|
||||
snapshot.empty.should.equal(false);
|
||||
snapshot.metadata.should.be.an.Object();
|
||||
snapshot.query.should.be.an.Object();
|
||||
});
|
||||
|
||||
it('DocumentChange has correct properties', async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
.collection('collection-tests');
|
||||
|
||||
// Test
|
||||
|
||||
let unsubscribe;
|
||||
let changes;
|
||||
await new Promise(resolve2 => {
|
||||
unsubscribe = collectionRef.onSnapshot(snapshot => {
|
||||
changes = snapshot.docChanges;
|
||||
resolve2();
|
||||
});
|
||||
});
|
||||
|
||||
// Assertions
|
||||
|
||||
changes.should.be.a.Array();
|
||||
changes[0].doc.should.be.an.Object();
|
||||
changes[0].newIndex.should.be.a.Number();
|
||||
changes[0].oldIndex.should.be.a.Number();
|
||||
changes[0].type.should.be.a.String();
|
||||
|
||||
// Tear down
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it('calls callback with the initial data and then when document changes', async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
|
@ -104,9 +163,7 @@ function collectionReferenceTests({
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('calls callback with the initial data and then when document is added', async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
|
@ -144,9 +201,7 @@ function collectionReferenceTests({
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it("doesn't call callback when the ref is updated with the same value", async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
|
@ -181,9 +236,7 @@ function collectionReferenceTests({
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('allows binding multiple callbacks to the same ref', async () => {
|
||||
// Setup
|
||||
const collectionRef = firebase.native
|
||||
|
@ -234,9 +287,7 @@ function collectionReferenceTests({
|
|||
unsubscribeA();
|
||||
unsubscribeB();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('listener stops listening when unsubscribed', async () => {
|
||||
// Setup
|
||||
const collectionRef = firebase.native
|
||||
|
@ -310,9 +361,7 @@ function collectionReferenceTests({
|
|||
callbackA.should.be.calledTwice();
|
||||
callbackB.should.be.calledThrice();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('supports options and callback', async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
|
@ -354,9 +403,7 @@ function collectionReferenceTests({
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('supports observer', async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
|
@ -396,9 +443,7 @@ function collectionReferenceTests({
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('supports options and observer', async () => {
|
||||
const collectionRef = firebase.native
|
||||
.firestore()
|
||||
|
@ -416,6 +461,7 @@ function collectionReferenceTests({
|
|||
snapshot.forEach(doc => callback(doc.data()));
|
||||
resolve2();
|
||||
},
|
||||
error: () => {},
|
||||
};
|
||||
unsubscribe = collectionRef.onSnapshot(
|
||||
{
|
||||
|
@ -443,6 +489,91 @@ function collectionReferenceTests({
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it('errors when invalid parameters supplied', async () => {
|
||||
const colRef = firebase.native
|
||||
.firestore()
|
||||
.collection('collection-tests');
|
||||
|
||||
(() => {
|
||||
colRef.onSnapshot(() => {}, 'error');
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Second argument must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot({
|
||||
next: () => {},
|
||||
error: 'error',
|
||||
});
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot({
|
||||
next: 'error',
|
||||
});
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot(
|
||||
{
|
||||
includeQueryMetadataChanges: true,
|
||||
},
|
||||
() => {},
|
||||
'error'
|
||||
);
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Third argument must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot(
|
||||
{
|
||||
includeQueryMetadataChanges: true,
|
||||
},
|
||||
{
|
||||
next: () => {},
|
||||
error: 'error',
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot(
|
||||
{
|
||||
includeQueryMetadataChanges: true,
|
||||
},
|
||||
{
|
||||
next: 'error',
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot(
|
||||
{
|
||||
includeQueryMetadataChanges: true,
|
||||
},
|
||||
'error'
|
||||
);
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Second argument must be a function or observer.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot({
|
||||
error: 'error',
|
||||
});
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: First argument must be a function, observer or options.'
|
||||
);
|
||||
(() => {
|
||||
colRef.onSnapshot();
|
||||
}).should.throw(
|
||||
'Query.onSnapshot failed: Called with invalid arguments.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Where
|
||||
|
@ -1005,6 +1136,56 @@ function collectionReferenceTests({
|
|||
});
|
||||
});
|
||||
|
||||
context('orderBy()', () => {
|
||||
it('errors if called after startAt', () => {
|
||||
(() => {
|
||||
firebase.native
|
||||
.firestore()
|
||||
.collection('collections')
|
||||
.startAt({})
|
||||
.orderBy('test');
|
||||
}).should.throw(
|
||||
'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors if called after startAfter', () => {
|
||||
(() => {
|
||||
firebase.native
|
||||
.firestore()
|
||||
.collection('collections')
|
||||
.startAfter({})
|
||||
.orderBy('test');
|
||||
}).should.throw(
|
||||
'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors if called after endBefore', () => {
|
||||
(() => {
|
||||
firebase.native
|
||||
.firestore()
|
||||
.collection('collections')
|
||||
.endBefore({})
|
||||
.orderBy('test');
|
||||
}).should.throw(
|
||||
'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().'
|
||||
);
|
||||
});
|
||||
|
||||
it('errors if called after endAt', () => {
|
||||
(() => {
|
||||
firebase.native
|
||||
.firestore()
|
||||
.collection('collections')
|
||||
.endAt({})
|
||||
.orderBy('test');
|
||||
}).should.throw(
|
||||
'Cannot specify an orderBy() constraint after calling startAt(), startAfter(), endBefore() or endAt().'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('gets called correctly', async () => {
|
||||
const collectionRef = collectionTests
|
||||
|
|
|
@ -17,6 +17,28 @@ function documentReferenceTests({ describe, it, context, firebase }) {
|
|||
}));
|
||||
});
|
||||
|
||||
context('id', () => {
|
||||
it('should return document id', () => {
|
||||
const document = firebase.native.firestore().doc('documents/doc1');
|
||||
document.id.should.equal('doc1');
|
||||
});
|
||||
});
|
||||
|
||||
context('parent', () => {
|
||||
it('should return parent collection', () => {
|
||||
const document = firebase.native.firestore().doc('documents/doc1');
|
||||
document.parent.id.should.equal('documents');
|
||||
});
|
||||
});
|
||||
|
||||
context('collection()', () => {
|
||||
it('should return a child collection', () => {
|
||||
const document = firebase.native.firestore().doc('documents/doc1');
|
||||
const collection = document.collection('pages');
|
||||
collection.id.should.equal('pages');
|
||||
});
|
||||
});
|
||||
|
||||
context('delete()', () => {
|
||||
it('should delete Document', () =>
|
||||
firebase.native
|
||||
|
@ -32,6 +54,17 @@ function documentReferenceTests({ describe, it, context, firebase }) {
|
|||
}));
|
||||
});
|
||||
|
||||
context('get()', () => {
|
||||
it('DocumentSnapshot should have correct properties', async () => {
|
||||
const snapshot = await firebase.native
|
||||
.firestore()
|
||||
.doc('document-tests/doc1')
|
||||
.get();
|
||||
snapshot.id.should.equal('doc1');
|
||||
snapshot.metadata.should.be.an.Object();
|
||||
});
|
||||
});
|
||||
|
||||
context('onSnapshot()', () => {
|
||||
it('calls callback with the initial data and then when value changes', async () => {
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
|
@ -321,6 +354,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
|
|||
callback(snapshot.data());
|
||||
resolve2();
|
||||
},
|
||||
error: () => {},
|
||||
};
|
||||
unsubscribe = docRef.onSnapshot(
|
||||
{ includeMetadataChanges: true },
|
||||
|
@ -346,6 +380,88 @@ function documentReferenceTests({ describe, it, context, firebase }) {
|
|||
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it('errors when invalid parameters supplied', async () => {
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
(() => {
|
||||
docRef.onSnapshot(() => {}, 'error');
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Second argument must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot({
|
||||
next: () => {},
|
||||
error: 'error',
|
||||
});
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot({
|
||||
next: 'error',
|
||||
});
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot(
|
||||
{
|
||||
includeMetadataChanges: true,
|
||||
},
|
||||
() => {},
|
||||
'error'
|
||||
);
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Third argument must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot(
|
||||
{
|
||||
includeMetadataChanges: true,
|
||||
},
|
||||
{
|
||||
next: () => {},
|
||||
error: 'error',
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Observer.error must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot(
|
||||
{
|
||||
includeMetadataChanges: true,
|
||||
},
|
||||
{
|
||||
next: 'error',
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Observer.next must be a valid function.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot(
|
||||
{
|
||||
includeMetadataChanges: true,
|
||||
},
|
||||
'error'
|
||||
);
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Second argument must be a function or observer.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot({
|
||||
error: 'error',
|
||||
});
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: First argument must be a function, observer or options.'
|
||||
);
|
||||
(() => {
|
||||
docRef.onSnapshot();
|
||||
}).should.throw(
|
||||
'DocumentReference.onSnapshot failed: Called with invalid arguments.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('set()', () => {
|
||||
|
@ -464,6 +580,25 @@ function documentReferenceTests({ describe, it, context, firebase }) {
|
|||
doc.data().nested.firstname.should.equal('First Name');
|
||||
doc.data().nested.lastname.should.equal('Last Name');
|
||||
}));
|
||||
|
||||
it('errors when invalid parameters supplied', async () => {
|
||||
const docRef = firebase.native.firestore().doc('document-tests/doc1');
|
||||
(() => {
|
||||
docRef.update('error');
|
||||
}).should.throw(
|
||||
'DocumentReference.update failed: If using a single argument, it must be an object.'
|
||||
);
|
||||
(() => {
|
||||
docRef.update('error1', 'error2', 'error3');
|
||||
}).should.throw(
|
||||
'DocumentReference.update failed: Must have either a single object argument, or equal numbers of key/value pairs.'
|
||||
);
|
||||
(() => {
|
||||
docRef.update(0, 'error');
|
||||
}).should.throw(
|
||||
'DocumentReference.update failed: Argument at index 0 must be a string or FieldPath'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('types', () => {
|
||||
|
|
|
@ -2,6 +2,13 @@ import should from 'should';
|
|||
|
||||
function fieldPathTests({ describe, it, context, firebase }) {
|
||||
describe('FieldPath', () => {
|
||||
context('documentId', () => {
|
||||
it('should be a FieldPath', () => {
|
||||
const documentId = firebase.native.firestore.FieldPath.documentId();
|
||||
documentId.should.be.instanceof(firebase.native.firestore.FieldPath);
|
||||
});
|
||||
});
|
||||
|
||||
context('DocumentSnapshot.get()', () => {
|
||||
it('should get the correct values', () =>
|
||||
firebase.native
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import should from 'should';
|
||||
|
||||
function firestoreTests({ describe, it, context, firebase }) {
|
||||
function firestoreTests({ describe, it, context, fcontext, firebase }) {
|
||||
describe('firestore()', () => {
|
||||
context('collection()', () => {
|
||||
it('should create CollectionReference with the right id', () =>
|
||||
|
@ -11,6 +11,14 @@ function firestoreTests({ describe, it, context, firebase }) {
|
|||
should.equal(collectionRef.id, 'collection2');
|
||||
resolve();
|
||||
}));
|
||||
|
||||
it('should error if invalid collection path supplied', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().collection('collection1/doc1');
|
||||
}).should.throw(
|
||||
'Argument "collectionPath" must point to a collection.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('doc()', () => {
|
||||
|
@ -22,6 +30,12 @@ function firestoreTests({ describe, it, context, firebase }) {
|
|||
should.equal(docRef.path, 'collection1/doc1/collection2/doc2');
|
||||
resolve();
|
||||
}));
|
||||
|
||||
it('should error if invalid document path supplied', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().doc('collection1');
|
||||
}).should.throw('Argument "documentPath" must point to a document.');
|
||||
});
|
||||
});
|
||||
|
||||
context('batch()', () => {
|
||||
|
@ -90,6 +104,148 @@ function firestoreTests({ describe, it, context, firebase }) {
|
|||
sfDoc.data().nested.lastname.should.equal('Last Name');
|
||||
});
|
||||
});
|
||||
|
||||
it('errors when invalid parameters supplied', async () => {
|
||||
const ref = firebase.native.firestore().doc('collection/doc');
|
||||
const batch = firebase.native.firestore().batch();
|
||||
(() => {
|
||||
batch.update(ref, 'error');
|
||||
}).should.throw(
|
||||
'WriteBatch.update failed: If using two arguments, the second must be an object.'
|
||||
);
|
||||
(() => {
|
||||
batch.update(ref, 'error1', 'error2', 'error3');
|
||||
}).should.throw(
|
||||
'WriteBatch.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.'
|
||||
);
|
||||
(() => {
|
||||
batch.update(ref, 0, 'error');
|
||||
}).should.throw(
|
||||
'WriteBatch.update failed: Argument at index 0 must be a string or FieldPath'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('disableNetwork()', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().disableNetwork();
|
||||
}).should.throw(
|
||||
'firebase.firestore().disableNetwork() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('enableNetwork()', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().enableNetwork();
|
||||
}).should.throw(
|
||||
'firebase.firestore().enableNetwork() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('enablePersistence()', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().enablePersistence();
|
||||
}).should.throw(
|
||||
'Persistence is enabled by default on the Firestore SDKs'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('setLogLevel()', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().setLogLevel();
|
||||
}).should.throw(
|
||||
'firebase.firestore().setLogLevel() is unsupported by the native Firebase SDKs.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context('settings()', () => {
|
||||
it('should throw an unsupported error', () => {
|
||||
(() => {
|
||||
firebase.native.firestore().settings();
|
||||
}).should.throw('firebase.firestore().settings() coming soon');
|
||||
});
|
||||
});
|
||||
|
||||
context('runTransaction()', () => {
|
||||
it('should set, update and delete transactionally and allow a return value', async () => {
|
||||
let deleteMe = false;
|
||||
const firestore = firebase.native.firestore();
|
||||
|
||||
const docRef = firestore
|
||||
.collection('transactions')
|
||||
.doc(Date.now().toString());
|
||||
|
||||
const updateFunction = async transaction => {
|
||||
const doc = await transaction.get(docRef);
|
||||
if (doc.exists && deleteMe) {
|
||||
transaction.delete(docRef);
|
||||
return 'bye';
|
||||
}
|
||||
|
||||
if (!doc.exists) {
|
||||
transaction.set(docRef, { value: 1 });
|
||||
return 1;
|
||||
}
|
||||
|
||||
const newValue = doc.data().value + 1;
|
||||
|
||||
if (newValue > 2) {
|
||||
return Promise.reject(
|
||||
new Error('Value should not be greater than 2!')
|
||||
);
|
||||
}
|
||||
|
||||
transaction.update(docRef, {
|
||||
value: newValue,
|
||||
somethingElse: 'update',
|
||||
});
|
||||
|
||||
return newValue;
|
||||
};
|
||||
|
||||
// set tests
|
||||
const val1 = await firestore.runTransaction(updateFunction);
|
||||
should.equal(val1, 1);
|
||||
const doc1 = await docRef.get();
|
||||
doc1.data().value.should.equal(1);
|
||||
should.equal(doc1.data().somethingElse, undefined);
|
||||
|
||||
// update
|
||||
const val2 = await firestore.runTransaction(updateFunction);
|
||||
should.equal(val2, 2);
|
||||
const doc2 = await docRef.get();
|
||||
doc2.data().value.should.equal(2);
|
||||
doc2.data().somethingElse.should.equal('update');
|
||||
|
||||
// rejecting / cancelling transaction
|
||||
let didReject = false;
|
||||
try {
|
||||
await firestore.runTransaction(updateFunction);
|
||||
} catch (e) {
|
||||
didReject = true;
|
||||
}
|
||||
should.equal(didReject, true);
|
||||
const doc3 = await docRef.get();
|
||||
doc3.data().value.should.equal(2);
|
||||
doc3.data().somethingElse.should.equal('update');
|
||||
|
||||
// delete
|
||||
deleteMe = true;
|
||||
const val4 = await firestore.runTransaction(updateFunction);
|
||||
should.equal(val4, 'bye');
|
||||
const doc4 = await docRef.get();
|
||||
should.equal(doc4.exists, false);
|
||||
|
||||
return Promise.resolve('Test Completed');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue