[database][wip] refactor & improvements to add support for multiple apps
This commit is contained in:
parent
6b7647c4f5
commit
e3d1261973
|
@ -77,15 +77,14 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param name
|
* @param name
|
||||||
* @param refId
|
|
||||||
* @param listenerId
|
|
||||||
* @param path
|
* @param path
|
||||||
* @param dataSnapshot
|
* @param dataSnapshot
|
||||||
|
* @param refId
|
||||||
|
* @param listenerId
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static WritableMap snapshotToMap(String name, int refId, Integer listenerId, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
public static WritableMap snapshotToMap(String name, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName, int refId, int listenerId) {
|
||||||
WritableMap snapshot = Arguments.createMap();
|
WritableMap snapshot = Arguments.createMap();
|
||||||
WritableMap eventMap = Arguments.createMap();
|
WritableMap eventMap = Arguments.createMap();
|
||||||
|
|
||||||
|
@ -109,19 +108,16 @@ public class Utils {
|
||||||
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
|
mapPutValue("priority", dataSnapshot.getPriority(), snapshot);
|
||||||
|
|
||||||
eventMap.putInt("refId", refId);
|
eventMap.putInt("refId", refId);
|
||||||
if (listenerId != null) {
|
|
||||||
eventMap.putInt("listenerId", listenerId);
|
|
||||||
}
|
|
||||||
eventMap.putString("path", path);
|
eventMap.putString("path", path);
|
||||||
eventMap.putMap("snapshot", snapshot);
|
eventMap.putMap("snapshot", snapshot);
|
||||||
eventMap.putString("eventName", name);
|
eventMap.putString("eventName", name);
|
||||||
|
eventMap.putInt("listenerId", listenerId);
|
||||||
eventMap.putString("previousChildName", previousChildName);
|
eventMap.putString("previousChildName", previousChildName);
|
||||||
|
|
||||||
return eventMap;
|
return eventMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param dataSnapshot
|
* @param dataSnapshot
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -151,7 +147,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param snapshot
|
* @param snapshot
|
||||||
* @param <Any>
|
* @param <Any>
|
||||||
* @return
|
* @return
|
||||||
|
@ -182,7 +177,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param mutableData
|
* @param mutableData
|
||||||
* @param <Any>
|
* @param <Any>
|
||||||
* @return
|
* @return
|
||||||
|
@ -216,7 +210,7 @@ public class Utils {
|
||||||
* Data should be treated as an array if:
|
* Data should be treated as an array if:
|
||||||
* 1) All the keys are integers
|
* 1) All the keys are integers
|
||||||
* 2) More than half the keys between 0 and the maximum key in the object have non-empty values
|
* 2) More than half the keys between 0 and the maximum key in the object have non-empty values
|
||||||
*
|
* <p>
|
||||||
* Definition from: https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html
|
* Definition from: https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html
|
||||||
*
|
*
|
||||||
* @param snapshot
|
* @param snapshot
|
||||||
|
@ -244,7 +238,7 @@ public class Utils {
|
||||||
* Data should be treated as an array if:
|
* Data should be treated as an array if:
|
||||||
* 1) All the keys are integers
|
* 1) All the keys are integers
|
||||||
* 2) More than half the keys between 0 and the maximum key in the object have non-empty values
|
* 2) More than half the keys between 0 and the maximum key in the object have non-empty values
|
||||||
*
|
* <p>
|
||||||
* Definition from: https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html
|
* Definition from: https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html
|
||||||
*
|
*
|
||||||
* @param mutableData
|
* @param mutableData
|
||||||
|
@ -269,7 +263,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param snapshot
|
* @param snapshot
|
||||||
* @param <Any>
|
* @param <Any>
|
||||||
* @return
|
* @return
|
||||||
|
@ -316,7 +309,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param mutableData
|
* @param mutableData
|
||||||
* @param <Any>
|
* @param <Any>
|
||||||
* @return
|
* @return
|
||||||
|
@ -363,7 +355,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param snapshot
|
* @param snapshot
|
||||||
* @param <Any>
|
* @param <Any>
|
||||||
* @return
|
* @return
|
||||||
|
@ -401,7 +392,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param mutableData
|
* @param mutableData
|
||||||
* @param <Any>
|
* @param <Any>
|
||||||
* @return
|
* @return
|
||||||
|
@ -439,7 +429,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param snapshot
|
* @param snapshot
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,252 +1,124 @@
|
||||||
package io.invertase.firebase.database;
|
package io.invertase.firebase.database;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.util.Log;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Callback;
|
import com.facebook.react.bridge.Promise;
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
import com.facebook.react.bridge.WritableArray;
|
|
||||||
import com.facebook.react.bridge.WritableMap;
|
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
import com.facebook.react.bridge.ReactContext;
|
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.WritableNativeArray;
|
import com.google.firebase.FirebaseApp;
|
||||||
import com.google.firebase.database.DataSnapshot;
|
|
||||||
import com.google.firebase.database.MutableData;
|
import com.google.firebase.database.MutableData;
|
||||||
import com.google.firebase.database.ServerValue;
|
|
||||||
import com.google.firebase.database.OnDisconnect;
|
import com.google.firebase.database.OnDisconnect;
|
||||||
import com.google.firebase.database.DatabaseError;
|
import com.google.firebase.database.ServerValue;
|
||||||
import com.google.firebase.database.DatabaseReference;
|
|
||||||
import com.google.firebase.database.FirebaseDatabase;
|
|
||||||
import com.google.firebase.database.DatabaseException;
|
|
||||||
import com.google.firebase.database.Transaction;
|
import com.google.firebase.database.Transaction;
|
||||||
|
import com.google.firebase.database.DataSnapshot;
|
||||||
|
import com.google.firebase.database.DatabaseError;
|
||||||
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
import com.google.firebase.database.DatabaseReference;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import io.invertase.firebase.Utils;
|
import io.invertase.firebase.Utils;
|
||||||
|
|
||||||
|
|
||||||
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||||
private static final String TAG = "RNFirebaseDatabase";
|
private static final String TAG = "RNFirebaseDatabase";
|
||||||
private HashMap<Integer, RNFirebaseDatabaseReference> mReferences = new HashMap<>();
|
private SparseArray<RNFirebaseDatabaseReference> references = new SparseArray<>();
|
||||||
private HashMap<String, RNFirebaseTransactionHandler> mTransactionHandlers = new HashMap<>();
|
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||||
private FirebaseDatabase mFirebaseDatabase;
|
|
||||||
|
|
||||||
public RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
mFirebaseDatabase = FirebaseDatabase.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/*
|
||||||
public String getName() {
|
* REACT NATIVE METHODS
|
||||||
return TAG;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
// Persistence
|
/**
|
||||||
|
* @param appName
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void enablePersistence(
|
public void goOnline(String appName) {
|
||||||
final Boolean enable,
|
getDatabaseForApp(appName).goOnline();
|
||||||
final Callback callback) {
|
|
||||||
try {
|
|
||||||
mFirebaseDatabase.setPersistenceEnabled(enable);
|
|
||||||
} catch (DatabaseException t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
WritableMap res = Arguments.createMap();
|
|
||||||
res.putString("status", "success");
|
|
||||||
callback.invoke(null, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void keepSynced(
|
public void goOffline(String appName) {
|
||||||
final String path,
|
getDatabaseForApp(appName).goOffline();
|
||||||
final Boolean enable,
|
|
||||||
final Callback callback) {
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
ref.keepSynced(enable);
|
|
||||||
|
|
||||||
WritableMap res = Arguments.createMap();
|
|
||||||
res.putString("status", "success");
|
|
||||||
res.putString("path", path);
|
|
||||||
callback.invoke(null, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RNFirebaseDatabase
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void set(
|
public void setPersistence(String appName, Boolean state) {
|
||||||
final String path,
|
getDatabaseForApp(appName).setPersistenceEnabled(state);
|
||||||
final ReadableMap props,
|
|
||||||
final Callback callback) {
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
|
||||||
|
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
|
||||||
handleCallback("set", callback, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ref.setValue(m.get("value"), listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void priority(
|
public void keepSynced(String appName, String path, Boolean state) {
|
||||||
final String path,
|
getReferenceForAppPath(appName, path).keepSynced(state);
|
||||||
final ReadableMap priority,
|
|
||||||
final Callback callback) {
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
Map<String, Object> priorityMap = Utils.recursivelyDeconstructReadableMap(priority);
|
|
||||||
|
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
|
||||||
handleCallback("priority", callback, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ref.setPriority(priorityMap.get("value"), listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TRANSACTIONS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param transactionId
|
||||||
|
* @param updates
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void withPriority(
|
public void transactionTryCommit(String appName, int transactionId, ReadableMap updates) {
|
||||||
final String path,
|
RNFirebaseTransactionHandler handler = transactionHandlers.get(transactionId);
|
||||||
final ReadableMap data,
|
|
||||||
final ReadableMap priority,
|
|
||||||
final Callback callback) {
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
Map<String, Object> dataMap = Utils.recursivelyDeconstructReadableMap(data);
|
|
||||||
Map<String, Object> priorityMap = Utils.recursivelyDeconstructReadableMap(priority);
|
|
||||||
|
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
if (handler != null) {
|
||||||
@Override
|
handler.signalUpdateReceived(updates);
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
|
||||||
handleCallback("withPriority", callback, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ref.setValue(dataMap.get("value"), priorityMap.get("value"), listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void update(final String path,
|
|
||||||
final ReadableMap props,
|
|
||||||
final Callback callback) {
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
|
||||||
|
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
|
||||||
handleCallback("update", callback, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ref.updateChildren(m, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void remove(final String path,
|
|
||||||
final Callback callback) {
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
|
||||||
handleCallback("remove", callback, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ref.removeValue(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void push(final String path,
|
|
||||||
final ReadableMap props,
|
|
||||||
final Callback callback) {
|
|
||||||
|
|
||||||
Log.d(TAG, "Called push with " + path);
|
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
|
||||||
DatabaseReference newRef = ref.push();
|
|
||||||
|
|
||||||
final Uri url = Uri.parse(newRef.toString());
|
|
||||||
final String newPath = url.getPath();
|
|
||||||
|
|
||||||
ReadableMapKeySetIterator iterator = props.keySetIterator();
|
|
||||||
if (iterator.hasNextKey()) {
|
|
||||||
Log.d(TAG, "Passed value to push");
|
|
||||||
// lame way to check if the `props` are empty
|
|
||||||
Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
|
||||||
|
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
|
||||||
if (error != null) {
|
|
||||||
WritableMap err = Arguments.createMap();
|
|
||||||
err.putInt("code", error.getCode());
|
|
||||||
err.putString("details", error.getDetails());
|
|
||||||
err.putString("description", error.getMessage());
|
|
||||||
callback.invoke(err);
|
|
||||||
} else {
|
|
||||||
WritableMap res = Arguments.createMap();
|
|
||||||
res.putString("status", "success");
|
|
||||||
res.putString("ref", newPath);
|
|
||||||
callback.invoke(null, res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
newRef.setValue(m.get("value"), listener);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "No value passed to push: " + newPath);
|
|
||||||
WritableMap res = Arguments.createMap();
|
|
||||||
res.putString("status", "success");
|
|
||||||
res.putString("ref", newPath);
|
|
||||||
callback.invoke(null, res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Start a native transaction and store it's state in
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
* @param path
|
* @param path
|
||||||
* @param id
|
* @param transactionId
|
||||||
* @param applyLocally
|
* @param applyLocally
|
||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void startTransaction(final String path, final String id, final Boolean applyLocally) {
|
public void transactionStart(final String appName, final String path, final int transactionId, final Boolean applyLocally) {
|
||||||
AsyncTask.execute(new Runnable() {
|
AsyncTask.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
DatabaseReference transactionRef = FirebaseDatabase.getInstance().getReference(path);
|
DatabaseReference reference = getReferenceForAppPath(appName, path);
|
||||||
|
|
||||||
transactionRef.runTransaction(new Transaction.Handler() {
|
reference.runTransaction(new Transaction.Handler() {
|
||||||
@Override
|
@Override
|
||||||
public Transaction.Result doTransaction(MutableData mutableData) {
|
public Transaction.Result doTransaction(MutableData mutableData) {
|
||||||
final WritableMap updatesMap = Arguments.createMap();
|
final RNFirebaseTransactionHandler transactionHandler = new RNFirebaseTransactionHandler(transactionId, appName);
|
||||||
|
transactionHandlers.put(transactionId, transactionHandler);
|
||||||
updatesMap.putString("id", id);
|
final WritableMap updatesMap = transactionHandler.createUpdateMap(mutableData);
|
||||||
updatesMap.putString("type", "update");
|
|
||||||
|
|
||||||
if (!mutableData.hasChildren()) {
|
|
||||||
Utils.mapPutValue("value", mutableData.getValue(), updatesMap);
|
|
||||||
} else {
|
|
||||||
Object value = Utils.castValue(mutableData);
|
|
||||||
if (value instanceof WritableNativeArray) {
|
|
||||||
updatesMap.putArray("value", (WritableArray) value);
|
|
||||||
} else {
|
|
||||||
updatesMap.putMap("value", (WritableMap) value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RNFirebaseTransactionHandler rnFirebaseTransactionHandler = new RNFirebaseTransactionHandler();
|
|
||||||
mTransactionHandlers.put(id, rnFirebaseTransactionHandler);
|
|
||||||
|
|
||||||
|
// emit the updates to js using an async task
|
||||||
|
// otherwise it gets blocked by the lock await
|
||||||
AsyncTask.execute(new Runnable() {
|
AsyncTask.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -254,245 +126,305 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// wait for js to return the updates (js calls transactionTryCommit)
|
||||||
try {
|
try {
|
||||||
rnFirebaseTransactionHandler.await();
|
transactionHandler.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
rnFirebaseTransactionHandler.interrupted = true;
|
transactionHandler.interrupted = true;
|
||||||
return Transaction.abort();
|
return Transaction.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rnFirebaseTransactionHandler.abort) {
|
if (transactionHandler.abort) {
|
||||||
return Transaction.abort();
|
return Transaction.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
mutableData.setValue(rnFirebaseTransactionHandler.value);
|
mutableData.setValue(transactionHandler.value);
|
||||||
return Transaction.success(mutableData);
|
return Transaction.success(mutableData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) {
|
public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) {
|
||||||
final WritableMap updatesMap = Arguments.createMap();
|
RNFirebaseTransactionHandler transactionHandler = transactionHandlers.get(transactionId);
|
||||||
updatesMap.putString("id", id);
|
WritableMap resultMap = transactionHandler.createResultMap(error, committed, snapshot);
|
||||||
|
Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", resultMap);
|
||||||
RNFirebaseTransactionHandler rnFirebaseTransactionHandler = mTransactionHandlers.get(id);
|
transactionHandlers.delete(transactionId);
|
||||||
|
|
||||||
// TODO error conversion util for database to create web sdk codes based on DatabaseError
|
|
||||||
if (databaseError != null) {
|
|
||||||
updatesMap.putString("type", "error");
|
|
||||||
|
|
||||||
updatesMap.putInt("code", databaseError.getCode());
|
|
||||||
updatesMap.putString("message", databaseError.getMessage());
|
|
||||||
} else if (rnFirebaseTransactionHandler.interrupted) {
|
|
||||||
updatesMap.putString("type", "error");
|
|
||||||
|
|
||||||
updatesMap.putInt("code", 666);
|
|
||||||
updatesMap.putString("message", "RNFirebase transaction was interrupted, aborting.");
|
|
||||||
} else {
|
|
||||||
updatesMap.putString("type", "complete");
|
|
||||||
updatesMap.putBoolean("committed", committed);
|
|
||||||
updatesMap.putMap("snapshot", Utils.snapshotToMap(dataSnapshot));
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", updatesMap);
|
|
||||||
mTransactionHandlers.remove(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, applyLocally);
|
}, applyLocally);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ON DISCONNECT
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Set a value on a ref when the client disconnects from the firebase server.
|
||||||
*
|
*
|
||||||
* @param id
|
* @param appName
|
||||||
* @param updates
|
* @param path
|
||||||
|
* @param props
|
||||||
|
* @param promise
|
||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void tryCommitTransaction(final String id, final ReadableMap updates) {
|
public void onDisconnectSet(String appName, String path, ReadableMap props, final Promise promise) {
|
||||||
Map<String, Object> updatesReturned = Utils.recursivelyDeconstructReadableMap(updates);
|
|
||||||
RNFirebaseTransactionHandler rnFirebaseTransactionHandler = mTransactionHandlers.get(id);
|
|
||||||
|
|
||||||
if (rnFirebaseTransactionHandler != null) {
|
|
||||||
rnFirebaseTransactionHandler.signalUpdateReceived(updatesReturned);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void on(final int refId, final String path, final ReadableArray modifiers, final int listenerId, final String eventName, final Callback callback) {
|
|
||||||
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
|
|
||||||
|
|
||||||
if (eventName.equals("value")) {
|
|
||||||
ref.addValueEventListener(listenerId);
|
|
||||||
} else {
|
|
||||||
ref.addChildEventListener(listenerId, eventName);
|
|
||||||
}
|
|
||||||
|
|
||||||
WritableMap resp = Arguments.createMap();
|
|
||||||
resp.putString("status", "success");
|
|
||||||
resp.putInt("refId", refId);
|
|
||||||
resp.putString("handle", path);
|
|
||||||
callback.invoke(null, resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void once(final int refId, final String path, final ReadableArray modifiers, final String eventName, final Callback callback) {
|
|
||||||
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
|
|
||||||
|
|
||||||
if (eventName.equals("value")) {
|
|
||||||
ref.addOnceValueEventListener(callback);
|
|
||||||
} else {
|
|
||||||
ref.addChildOnceEventListener(eventName, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* At the time of this writing, off() only gets called when there are no more subscribers to a given path.
|
|
||||||
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventNames, so
|
|
||||||
* it doesn't really matter- just polluting the RN bridge a little more than necessary.
|
|
||||||
* off() should therefore clean *everything* up
|
|
||||||
*/
|
|
||||||
@ReactMethod
|
|
||||||
public void off(
|
|
||||||
final int refId,
|
|
||||||
final ReadableArray listeners,
|
|
||||||
final Callback callback) {
|
|
||||||
|
|
||||||
RNFirebaseDatabaseReference r = mReferences.get(refId);
|
|
||||||
|
|
||||||
if (r != null) {
|
|
||||||
List<Object> listenersList = Utils.recursivelyDeconstructReadableArray(listeners);
|
|
||||||
|
|
||||||
for (Object l : listenersList) {
|
|
||||||
Map<String, Object> listener = (Map) l;
|
|
||||||
int listenerId = ((Double) listener.get("listenerId")).intValue();
|
|
||||||
String eventName = (String) listener.get("eventName");
|
|
||||||
r.removeEventListener(listenerId, eventName);
|
|
||||||
if (!r.hasListeners()) {
|
|
||||||
mReferences.remove(refId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, "Removed listeners refId: " + refId + " ; count: " + listeners.size());
|
|
||||||
WritableMap resp = Arguments.createMap();
|
|
||||||
resp.putInt("refId", refId);
|
|
||||||
resp.putString("status", "success");
|
|
||||||
callback.invoke(null, resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void onDisconnectSet(final String path, final ReadableMap props, final Callback callback) {
|
|
||||||
String type = props.getString("type");
|
String type = props.getString("type");
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
OnDisconnect od = ref.onDisconnect();
|
|
||||||
|
OnDisconnect onDisconnect = ref.onDisconnect();
|
||||||
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
handleCallback("onDisconnectSet", callback, error);
|
handlePromise(promise, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "object":
|
case "object":
|
||||||
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props.getMap("value"));
|
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props.getMap("value"));
|
||||||
od.setValue(map, listener);
|
onDisconnect.setValue(map, listener);
|
||||||
break;
|
break;
|
||||||
case "array":
|
case "array":
|
||||||
List<Object> list = Utils.recursivelyDeconstructReadableArray(props.getArray("value"));
|
List<Object> list = Utils.recursivelyDeconstructReadableArray(props.getArray("value"));
|
||||||
od.setValue(list, listener);
|
onDisconnect.setValue(list, listener);
|
||||||
break;
|
break;
|
||||||
case "string":
|
case "string":
|
||||||
od.setValue(props.getString("value"), listener);
|
onDisconnect.setValue(props.getString("value"), listener);
|
||||||
break;
|
break;
|
||||||
case "number":
|
case "number":
|
||||||
od.setValue(props.getDouble("value"), listener);
|
onDisconnect.setValue(props.getDouble("value"), listener);
|
||||||
break;
|
break;
|
||||||
case "boolean":
|
case "boolean":
|
||||||
od.setValue(props.getBoolean("value"), listener);
|
onDisconnect.setValue(props.getBoolean("value"), listener);
|
||||||
break;
|
break;
|
||||||
case "null":
|
case "null":
|
||||||
od.setValue(null, listener);
|
onDisconnect.setValue(null, listener);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a value on a ref when the client disconnects from the firebase server.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param props
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void onDisconnectUpdate(final String path, final ReadableMap props, final Callback callback) {
|
public void onDisconnectUpdate(String appName, String path, ReadableMap props, final Promise promise) {
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
OnDisconnect od = ref.onDisconnect();
|
OnDisconnect ondDisconnect = ref.onDisconnect();
|
||||||
|
|
||||||
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props);
|
Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props);
|
||||||
od.updateChildren(map, new DatabaseReference.CompletionListener() {
|
|
||||||
|
ondDisconnect.updateChildren(map, new DatabaseReference.CompletionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
handleCallback("onDisconnectUpdate", callback, error);
|
handlePromise(promise, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a ref when the client disconnects from the firebase server.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void onDisconnectRemove(final String path, final Callback callback) {
|
public void onDisconnectRemove(String appName, String path, final Promise promise) {
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
OnDisconnect onDisconnect = ref.onDisconnect();
|
||||||
|
|
||||||
OnDisconnect od = ref.onDisconnect();
|
onDisconnect.removeValue(new DatabaseReference.CompletionListener() {
|
||||||
od.removeValue(new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
handleCallback("onDisconnectRemove", callback, error);
|
handlePromise(promise, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a pending onDisconnect action.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void onDisconnectCancel(final String path, final Callback callback) {
|
public void onDisconnectCancel(String appName, String path, final Promise promise) {
|
||||||
DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
OnDisconnect onDisconnect = ref.onDisconnect();
|
||||||
|
|
||||||
OnDisconnect od = ref.onDisconnect();
|
onDisconnect.cancel(new DatabaseReference.CompletionListener() {
|
||||||
od.cancel(new DatabaseReference.CompletionListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
handleCallback("onDisconnectCancel", callback, error);
|
handlePromise(promise, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param props
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void goOnline() {
|
public void set(String appName, String path, ReadableMap props, final Promise promise) {
|
||||||
mFirebaseDatabase.goOnline();
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
Object value = Utils.recursivelyDeconstructReadableMap(props).get("value");
|
||||||
|
|
||||||
|
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ref.setValue(value, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param priority
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void goOffline() {
|
public void setPriority(String appName, String path, ReadableMap priority, final Promise promise) {
|
||||||
mFirebaseDatabase.goOffline();
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
Object priorityValue = Utils.recursivelyDeconstructReadableMap(priority).get("value");
|
||||||
|
|
||||||
|
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ref.setPriority(priorityValue, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCallback(
|
/**
|
||||||
final String methodName,
|
* @param appName
|
||||||
final Callback callback,
|
* @param path
|
||||||
final DatabaseError databaseError) {
|
* @param data
|
||||||
if (databaseError != null) {
|
* @param priority
|
||||||
WritableMap err = Arguments.createMap();
|
* @param promise
|
||||||
err.putInt("code", databaseError.getCode());
|
*/
|
||||||
err.putString("details", databaseError.getDetails());
|
@ReactMethod
|
||||||
err.putString("description", databaseError.getMessage());
|
public void setWithPriority(String appName, String path, ReadableMap data, ReadableMap priority, final Promise promise) {
|
||||||
callback.invoke(err);
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
Object dataValue = Utils.recursivelyDeconstructReadableMap(data).get("value");
|
||||||
|
Object priorityValue = Utils.recursivelyDeconstructReadableMap(priority).get("value");
|
||||||
|
|
||||||
|
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ref.setValue(dataValue, priorityValue, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param props
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void update(String appName, String path, ReadableMap props, final Promise promise) {
|
||||||
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
Map<String, Object> updates = Utils.recursivelyDeconstructReadableMap(props);
|
||||||
|
|
||||||
|
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ref.updateChildren(updates, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void remove(String appName, String path, final Promise promise) {
|
||||||
|
DatabaseReference ref = getReferenceForAppPath(appName, path);
|
||||||
|
|
||||||
|
DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ref.removeValue(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push no longer required, handled in JS now.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subcribe once to a firebase reference.
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param refId
|
||||||
|
* @param path
|
||||||
|
* @param modifiers
|
||||||
|
* @param eventName
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void once(String appName, int refId, String path, ReadableArray modifiers, String eventName, Promise promise) {
|
||||||
|
RNFirebaseDatabaseReference internalRef = getInternalReferenceForApp(appName, refId, path, modifiers, false);
|
||||||
|
|
||||||
|
if (eventName.equals("value")) {
|
||||||
|
internalRef.addOnceValueEventListener(promise);
|
||||||
} else {
|
} else {
|
||||||
WritableMap res = Arguments.createMap();
|
internalRef.addChildOnceEventListener(eventName, promise);
|
||||||
res.putString("status", "success");
|
|
||||||
res.putString("method", methodName);
|
|
||||||
callback.invoke(null, res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RNFirebaseDatabaseReference getDBHandle(final int refId, final String path,
|
|
||||||
final ReadableArray modifiers) {
|
|
||||||
RNFirebaseDatabaseReference r = mReferences.get(refId);
|
|
||||||
|
|
||||||
if (r == null) {
|
|
||||||
ReactContext ctx = getReactApplicationContext();
|
|
||||||
r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, refId, path, modifiers);
|
/*
|
||||||
mReferences.put(refId, r);
|
* INTERNALS/UTILS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve null or reject with a js like error if databaseError exists
|
||||||
|
*
|
||||||
|
* @param promise
|
||||||
|
* @param databaseError
|
||||||
|
*/
|
||||||
|
static void handlePromise(Promise promise, DatabaseError databaseError) {
|
||||||
|
if (databaseError != null) {
|
||||||
|
WritableMap jsError = getJSError(databaseError);
|
||||||
|
promise.reject(
|
||||||
|
jsError.getString("code"),
|
||||||
|
jsError.getString("message"),
|
||||||
|
databaseError.toException()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return r;
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "RNFirebaseDatabase";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -501,4 +433,130 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||||
constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
|
constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
|
||||||
return constants;
|
return constants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a database instance for a specific firebase app instance
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static FirebaseDatabase getDatabaseForApp(String appName) {
|
||||||
|
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
|
||||||
|
return FirebaseDatabase.getInstance(firebaseApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a database reference for a specific app and path
|
||||||
|
*
|
||||||
|
* @param appName
|
||||||
|
* @param path
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private DatabaseReference getReferenceForAppPath(String appName, String path) {
|
||||||
|
return getDatabaseForApp(appName).getReference(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param appName
|
||||||
|
* @param refId
|
||||||
|
* @param path
|
||||||
|
* @param modifiers
|
||||||
|
* @param keep
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private RNFirebaseDatabaseReference getInternalReferenceForApp(String appName, int refId, String path, ReadableArray modifiers, Boolean keep) {
|
||||||
|
RNFirebaseDatabaseReference existingRef = references.get(refId);
|
||||||
|
|
||||||
|
if (existingRef == null) {
|
||||||
|
existingRef = new RNFirebaseDatabaseReference(
|
||||||
|
getReactApplicationContext(),
|
||||||
|
appName,
|
||||||
|
refId,
|
||||||
|
path,
|
||||||
|
modifiers
|
||||||
|
);
|
||||||
|
|
||||||
|
if (keep) references.put(refId, existingRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo move to error util for use in other modules
|
||||||
|
static String getMessageWithService(String message, String service, String fullCode) {
|
||||||
|
// Service: Error message (service/code).
|
||||||
|
return service + ": " + message + " (" + fullCode.toLowerCase() + ").";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getCodeWithService(String service, String code) {
|
||||||
|
return service.toUpperCase() + "/" + code.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
static WritableMap getJSError(DatabaseError nativeError) {
|
||||||
|
WritableMap errorMap = Arguments.createMap();
|
||||||
|
errorMap.putInt("nativeErrorCode", nativeError.getCode());
|
||||||
|
errorMap.putString("nativeErrorMessage", nativeError.getMessage());
|
||||||
|
|
||||||
|
String code;
|
||||||
|
String message;
|
||||||
|
String service = "Database";
|
||||||
|
|
||||||
|
switch (nativeError.getCode()) {
|
||||||
|
case DatabaseError.DATA_STALE:
|
||||||
|
code = getCodeWithService(service, "data-stale");
|
||||||
|
message = getMessageWithService("The transaction needs to be run again with current data.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.OPERATION_FAILED:
|
||||||
|
code = getCodeWithService(service, "failure");
|
||||||
|
message = getMessageWithService("The server indicated that this operation failed.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.PERMISSION_DENIED:
|
||||||
|
code = getCodeWithService(service, "permission-denied");
|
||||||
|
message = getMessageWithService("Client doesn't have permission to access the desired data.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.DISCONNECTED:
|
||||||
|
code = getCodeWithService(service, "disconnected");
|
||||||
|
message = getMessageWithService("The operation had to be aborted due to a network disconnect.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.EXPIRED_TOKEN:
|
||||||
|
code = getCodeWithService(service, "expired-token");
|
||||||
|
message = getMessageWithService("The supplied auth token has expired.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.INVALID_TOKEN:
|
||||||
|
code = getCodeWithService(service, "invalid-token");
|
||||||
|
message = getMessageWithService("The supplied auth token was invalid.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.MAX_RETRIES:
|
||||||
|
code = getCodeWithService(service, "max-retries");
|
||||||
|
message = getMessageWithService("The transaction had too many retries.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.OVERRIDDEN_BY_SET:
|
||||||
|
code = getCodeWithService(service, "overridden-by-set");
|
||||||
|
message = getMessageWithService("The transaction was overridden by a subsequent set.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.UNAVAILABLE:
|
||||||
|
code = getCodeWithService(service, "unavailable");
|
||||||
|
message = getMessageWithService("The service is unavailable.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.USER_CODE_EXCEPTION:
|
||||||
|
code = getCodeWithService(service, "user-code-exception");
|
||||||
|
message = getMessageWithService("User code called from the Firebase Database runloop threw an exception.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.NETWORK_ERROR:
|
||||||
|
code = getCodeWithService(service, "network-error");
|
||||||
|
message = getMessageWithService("The operation could not be performed due to a network error.", service, code);
|
||||||
|
break;
|
||||||
|
case DatabaseError.WRITE_CANCELED:
|
||||||
|
code = getCodeWithService(service, "write-cancelled");
|
||||||
|
message = getMessageWithService("The write was canceled by the user.", service, code);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
code = getCodeWithService(service, "unknown");
|
||||||
|
message = getMessageWithService("An unknown error occurred", service, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMap.putString("code", code);
|
||||||
|
errorMap.putString("message", message);
|
||||||
|
return errorMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,504 @@
|
||||||
|
package io.invertase.firebase.database;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Callback;
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.ReactContext;
|
||||||
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.WritableNativeArray;
|
||||||
|
import com.google.firebase.database.DataSnapshot;
|
||||||
|
import com.google.firebase.database.MutableData;
|
||||||
|
import com.google.firebase.database.ServerValue;
|
||||||
|
import com.google.firebase.database.OnDisconnect;
|
||||||
|
import com.google.firebase.database.DatabaseError;
|
||||||
|
import com.google.firebase.database.DatabaseReference;
|
||||||
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
import com.google.firebase.database.DatabaseException;
|
||||||
|
import com.google.firebase.database.Transaction;
|
||||||
|
|
||||||
|
import io.invertase.firebase.Utils;
|
||||||
|
|
||||||
|
public class RNFirebaseDatabaseOld extends ReactContextBaseJavaModule {
|
||||||
|
// private static final String TAG = "RNFirebaseDatabase";
|
||||||
|
// private HashMap<Integer, RNFirebaseDatabaseReference> mReferences = new HashMap<>();
|
||||||
|
// private HashMap<String, RNFirebaseTransactionHandler> mTransactionHandlers = new HashMap<>();
|
||||||
|
// private FirebaseDatabase mFirebaseDatabase;
|
||||||
|
//
|
||||||
|
// public RNFirebaseDatabaseOld(ReactApplicationContext reactContext) {
|
||||||
|
// super(reactContext);
|
||||||
|
// mFirebaseDatabase = FirebaseDatabase.getInstance();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public String getName() {
|
||||||
|
// return TAG;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Persistence
|
||||||
|
// @ReactMethod
|
||||||
|
// public void enablePersistence(
|
||||||
|
// final Boolean enable,
|
||||||
|
// final Callback callback) {
|
||||||
|
// try {
|
||||||
|
// mFirebaseDatabase.setPersistenceEnabled(enable);
|
||||||
|
// } catch (DatabaseException t) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// WritableMap res = Arguments.createMap();
|
||||||
|
// res.putString("status", "success");
|
||||||
|
// callback.invoke(null, res);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void keepSynced(
|
||||||
|
// final String path,
|
||||||
|
// final Boolean enable,
|
||||||
|
// final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// ref.keepSynced(enable);
|
||||||
|
//
|
||||||
|
// WritableMap res = Arguments.createMap();
|
||||||
|
// res.putString("status", "success");
|
||||||
|
// res.putString("path", path);
|
||||||
|
// callback.invoke(null, res);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // RNFirebaseDatabase
|
||||||
|
// @ReactMethod
|
||||||
|
// public void set(
|
||||||
|
// final String path,
|
||||||
|
// final ReadableMap props,
|
||||||
|
// final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||||
|
//
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("set", callback, error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// ref.setValue(m.get("value"), listener);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void priority(
|
||||||
|
// final String path,
|
||||||
|
// final ReadableMap priority,
|
||||||
|
// final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// Map<String, Object> priorityMap = Utils.recursivelyDeconstructReadableMap(priority);
|
||||||
|
//
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("priority", callback, error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// ref.setPriority(priorityMap.get("value"), listener);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ReactMethod
|
||||||
|
// public void withPriority(
|
||||||
|
// final String path,
|
||||||
|
// final ReadableMap data,
|
||||||
|
// final ReadableMap priority,
|
||||||
|
// final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// Map<String, Object> dataMap = Utils.recursivelyDeconstructReadableMap(data);
|
||||||
|
// Map<String, Object> priorityMap = Utils.recursivelyDeconstructReadableMap(priority);
|
||||||
|
//
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("withPriority", callback, error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// ref.setValue(dataMap.get("value"), priorityMap.get("value"), listener);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ReactMethod
|
||||||
|
// public void update(final String path,
|
||||||
|
// final ReadableMap props,
|
||||||
|
// final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||||
|
//
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("update", callback, error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// ref.updateChildren(m, listener);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void remove(final String path,
|
||||||
|
// final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("remove", callback, error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// ref.removeValue(listener);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void push(final String path,
|
||||||
|
// final ReadableMap props,
|
||||||
|
// final Callback callback) {
|
||||||
|
//
|
||||||
|
// Log.d(TAG, "Called push with " + path);
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// DatabaseReference newRef = ref.push();
|
||||||
|
//
|
||||||
|
// final Uri url = Uri.parse(newRef.toString());
|
||||||
|
// final String newPath = url.getPath();
|
||||||
|
//
|
||||||
|
// ReadableMapKeySetIterator iterator = props.keySetIterator();
|
||||||
|
// if (iterator.hasNextKey()) {
|
||||||
|
// Log.d(TAG, "Passed value to push");
|
||||||
|
// // lame way to check if the `props` are empty
|
||||||
|
// Map<String, Object> m = Utils.recursivelyDeconstructReadableMap(props);
|
||||||
|
//
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// if (error != null) {
|
||||||
|
// WritableMap err = Arguments.createMap();
|
||||||
|
// err.putInt("code", error.getCode());
|
||||||
|
// err.putString("details", error.getDetails());
|
||||||
|
// err.putString("description", error.getMessage());
|
||||||
|
// callback.invoke(err);
|
||||||
|
// } else {
|
||||||
|
// WritableMap res = Arguments.createMap();
|
||||||
|
// res.putString("status", "success");
|
||||||
|
// res.putString("ref", newPath);
|
||||||
|
// callback.invoke(null, res);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// newRef.setValue(m.get("value"), listener);
|
||||||
|
// } else {
|
||||||
|
// Log.d(TAG, "No value passed to push: " + newPath);
|
||||||
|
// WritableMap res = Arguments.createMap();
|
||||||
|
// res.putString("status", "success");
|
||||||
|
// res.putString("ref", newPath);
|
||||||
|
// callback.invoke(null, res);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @param path
|
||||||
|
// * @param id
|
||||||
|
// * @param applyLocally
|
||||||
|
// */
|
||||||
|
// @ReactMethod
|
||||||
|
// public void startTransaction(final String path, final String id, final Boolean applyLocally) {
|
||||||
|
// AsyncTask.execute(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// DatabaseReference transactionRef = FirebaseDatabase.getInstance().getReference(path);
|
||||||
|
//
|
||||||
|
// transactionRef.runTransaction(new Transaction.Handler() {
|
||||||
|
// @Override
|
||||||
|
// public Transaction.Result doTransaction(MutableData mutableData) {
|
||||||
|
// final WritableMap updatesMap = Arguments.createMap();
|
||||||
|
//
|
||||||
|
// updatesMap.putString("id", id);
|
||||||
|
// updatesMap.putString("type", "update");
|
||||||
|
//
|
||||||
|
// if (!mutableData.hasChildren()) {
|
||||||
|
// Utils.mapPutValue("value", mutableData.getValue(), updatesMap);
|
||||||
|
// } else {
|
||||||
|
// Object value = Utils.castValue(mutableData);
|
||||||
|
// if (value instanceof WritableNativeArray) {
|
||||||
|
// updatesMap.putArray("value", (WritableArray) value);
|
||||||
|
// } else {
|
||||||
|
// updatesMap.putMap("value", (WritableMap) value);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// RNFirebaseTransactionHandler rnFirebaseTransactionHandler = new RNFirebaseTransactionHandler();
|
||||||
|
// mTransactionHandlers.put(id, rnFirebaseTransactionHandler);
|
||||||
|
//
|
||||||
|
// AsyncTask.execute(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", updatesMap);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// rnFirebaseTransactionHandler.await();
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// rnFirebaseTransactionHandler.interrupted = true;
|
||||||
|
// return Transaction.abort();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (rnFirebaseTransactionHandler.abort) {
|
||||||
|
// return Transaction.abort();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// mutableData.setValue(rnFirebaseTransactionHandler.value);
|
||||||
|
// return Transaction.success(mutableData);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) {
|
||||||
|
// final WritableMap updatesMap = Arguments.createMap();
|
||||||
|
// updatesMap.putString("id", id);
|
||||||
|
//
|
||||||
|
// RNFirebaseTransactionHandler rnFirebaseTransactionHandler = mTransactionHandlers.get(id);
|
||||||
|
//
|
||||||
|
// // TODO error conversion util for database to create web sdk codes based on DatabaseError
|
||||||
|
// if (databaseError != null) {
|
||||||
|
// updatesMap.putString("type", "error");
|
||||||
|
//
|
||||||
|
// updatesMap.putInt("code", databaseError.getCode());
|
||||||
|
// updatesMap.putString("message", databaseError.getMessage());
|
||||||
|
// } else if (rnFirebaseTransactionHandler.interrupted) {
|
||||||
|
// updatesMap.putString("type", "error");
|
||||||
|
//
|
||||||
|
// updatesMap.putInt("code", 666);
|
||||||
|
// updatesMap.putString("message", "RNFirebase transaction was interrupted, aborting.");
|
||||||
|
// } else {
|
||||||
|
// updatesMap.putString("type", "complete");
|
||||||
|
// updatesMap.putBoolean("committed", committed);
|
||||||
|
// updatesMap.putMap("snapshot", Utils.snapshotToMap(dataSnapshot));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Utils.sendEvent(getReactApplicationContext(), "database_transaction_event", updatesMap);
|
||||||
|
// mTransactionHandlers.remove(id);
|
||||||
|
// }
|
||||||
|
// }, applyLocally);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @param id
|
||||||
|
// * @param updates
|
||||||
|
// */
|
||||||
|
// @ReactMethod
|
||||||
|
// public void tryCommitTransaction(final String id, final ReadableMap updates) {
|
||||||
|
// Map<String, Object> updatesReturned = Utils.recursivelyDeconstructReadableMap(updates);
|
||||||
|
// RNFirebaseTransactionHandler rnFirebaseTransactionHandler = mTransactionHandlers.get(id);
|
||||||
|
//
|
||||||
|
// if (rnFirebaseTransactionHandler != null) {
|
||||||
|
// rnFirebaseTransactionHandler.signalUpdateReceived(updatesReturned);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void on(final int refId, final String path, final ReadableArray modifiers, final int listenerId, final String eventName, final Callback callback) {
|
||||||
|
RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
|
||||||
|
|
||||||
|
if (eventName.equals("value")) {
|
||||||
|
ref.addValueEventListener(listenerId);
|
||||||
|
} else {
|
||||||
|
ref.addChildEventListener(listenerId, eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableMap resp = Arguments.createMap();
|
||||||
|
resp.putString("status", "success");
|
||||||
|
resp.putInt("refId", refId);
|
||||||
|
resp.putString("handle", path);
|
||||||
|
callback.invoke(null, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void once(final int refId, final String path, final ReadableArray modifiers, final String eventName, final Callback callback) {
|
||||||
|
// RNFirebaseDatabaseReference ref = this.getDBHandle(refId, path, modifiers);
|
||||||
|
//
|
||||||
|
// if (eventName.equals("value")) {
|
||||||
|
// ref.addOnceValueEventListener(callback);
|
||||||
|
// } else {
|
||||||
|
// ref.addChildOnceEventListener(eventName, callback);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At the time of this writing, off() only gets called when there are no more subscribers to a given path.
|
||||||
|
* `mListeners` might therefore be out of sync (though javascript isnt listening for those eventNames, so
|
||||||
|
* it doesn't really matter- just polluting the RN bridge a little more than necessary.
|
||||||
|
* off() should therefore clean *everything* up
|
||||||
|
*/
|
||||||
|
@ReactMethod
|
||||||
|
public void off(
|
||||||
|
final int refId,
|
||||||
|
final ReadableArray listeners,
|
||||||
|
final Callback callback) {
|
||||||
|
|
||||||
|
RNFirebaseDatabaseReference r = mReferences.get(refId);
|
||||||
|
|
||||||
|
if (r != null) {
|
||||||
|
List<Object> listenersList = Utils.recursivelyDeconstructReadableArray(listeners);
|
||||||
|
|
||||||
|
for (Object l : listenersList) {
|
||||||
|
Map<String, Object> listener = (Map) l;
|
||||||
|
int listenerId = ((Double) listener.get("listenerId")).intValue();
|
||||||
|
String eventName = (String) listener.get("eventName");
|
||||||
|
r.removeEventListener(listenerId, eventName);
|
||||||
|
if (!r.hasListeners()) {
|
||||||
|
mReferences.remove(refId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Removed listeners refId: " + refId + " ; count: " + listeners.size());
|
||||||
|
WritableMap resp = Arguments.createMap();
|
||||||
|
resp.putInt("refId", refId);
|
||||||
|
resp.putString("status", "success");
|
||||||
|
callback.invoke(null, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void onDisconnectSet(final String path, final ReadableMap props, final Callback callback) {
|
||||||
|
// String type = props.getString("type");
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// OnDisconnect od = ref.onDisconnect();
|
||||||
|
// DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("onDisconnectSet", callback, error);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// switch (type) {
|
||||||
|
// case "object":
|
||||||
|
// Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props.getMap("value"));
|
||||||
|
// od.setValue(map, listener);
|
||||||
|
// break;
|
||||||
|
// case "array":
|
||||||
|
// List<Object> list = Utils.recursivelyDeconstructReadableArray(props.getArray("value"));
|
||||||
|
// od.setValue(list, listener);
|
||||||
|
// break;
|
||||||
|
// case "string":
|
||||||
|
// od.setValue(props.getString("value"), listener);
|
||||||
|
// break;
|
||||||
|
// case "number":
|
||||||
|
// od.setValue(props.getDouble("value"), listener);
|
||||||
|
// break;
|
||||||
|
// case "boolean":
|
||||||
|
// od.setValue(props.getBoolean("value"), listener);
|
||||||
|
// break;
|
||||||
|
// case "null":
|
||||||
|
// od.setValue(null, listener);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ReactMethod
|
||||||
|
// public void onDisconnectUpdate(final String path, final ReadableMap props, final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
// OnDisconnect od = ref.onDisconnect();
|
||||||
|
// Map<String, Object> map = Utils.recursivelyDeconstructReadableMap(props);
|
||||||
|
// od.updateChildren(map, new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("onDisconnectUpdate", callback, error);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ReactMethod
|
||||||
|
// public void onDisconnectRemove(final String path, final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
//
|
||||||
|
// OnDisconnect od = ref.onDisconnect();
|
||||||
|
// od.removeValue(new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("onDisconnectRemove", callback, error);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ReactMethod
|
||||||
|
// public void onDisconnectCancel(final String path, final Callback callback) {
|
||||||
|
// DatabaseReference ref = mFirebaseDatabase.getReference(path);
|
||||||
|
//
|
||||||
|
// OnDisconnect od = ref.onDisconnect();
|
||||||
|
// od.cancel(new DatabaseReference.CompletionListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onComplete(DatabaseError error, DatabaseReference ref) {
|
||||||
|
// handleCallback("onDisconnectCancel", callback, error);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @ReactMethod
|
||||||
|
// public void goOnline() {
|
||||||
|
// mFirebaseDatabase.goOnline();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @ReactMethod
|
||||||
|
// public void goOffline() {
|
||||||
|
// mFirebaseDatabase.goOffline();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private void handleCallback(
|
||||||
|
// final String methodName,
|
||||||
|
// final Callback callback,
|
||||||
|
// final DatabaseError databaseError) {
|
||||||
|
// if (databaseError != null) {
|
||||||
|
// WritableMap err = Arguments.createMap();
|
||||||
|
// err.putInt("code", databaseError.getCode());
|
||||||
|
// err.putString("details", databaseError.getDetails());
|
||||||
|
// err.putString("description", databaseError.getMessage());
|
||||||
|
// callback.invoke(err);
|
||||||
|
// } else {
|
||||||
|
// WritableMap res = Arguments.createMap();
|
||||||
|
// res.putString("status", "success");
|
||||||
|
// res.putString("method", methodName);
|
||||||
|
// callback.invoke(null, res);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private RNFirebaseDatabaseReference getDBHandle(final int refId, final String path,
|
||||||
|
// final ReadableArray modifiers) {
|
||||||
|
// RNFirebaseDatabaseReference r = mReferences.get(refId);
|
||||||
|
//
|
||||||
|
// if (r == null) {
|
||||||
|
// ReactContext ctx = getReactApplicationContext();
|
||||||
|
// r = new RNFirebaseDatabaseReference(ctx, mFirebaseDatabase, refId, path, modifiers);
|
||||||
|
// mReferences.put(refId, r);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return r;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public Map<String, Object> getConstants() {
|
||||||
|
// final Map<String, Object> constants = new HashMap<>();
|
||||||
|
// constants.put("serverValueTimestamp", ServerValue.TIMESTAMP);
|
||||||
|
// return constants;
|
||||||
|
// }
|
||||||
|
}
|
|
@ -7,8 +7,8 @@ import android.util.Log;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Callback;
|
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.bridge.ReactContext;
|
import com.facebook.react.bridge.ReactContext;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
|
@ -25,28 +25,125 @@ import io.invertase.firebase.Utils;
|
||||||
public class RNFirebaseDatabaseReference {
|
public class RNFirebaseDatabaseReference {
|
||||||
private static final String TAG = "RNFirebaseDBReference";
|
private static final String TAG = "RNFirebaseDBReference";
|
||||||
|
|
||||||
private int mRefId;
|
private int refId;
|
||||||
private String mPath;
|
private Query query;
|
||||||
private Query mQuery;
|
private String path;
|
||||||
private ReactContext mReactContext;
|
private String appName;
|
||||||
private SparseArray<ChildEventListener> mChildEventListeners;
|
private ReactContext reactContext;
|
||||||
private SparseArray<ValueEventListener> mValueEventListeners;
|
private SparseArray<ChildEventListener> childEventListeners;
|
||||||
|
private SparseArray<ValueEventListener> valueEventListeners;
|
||||||
|
|
||||||
RNFirebaseDatabaseReference(final ReactContext context,
|
/**
|
||||||
final FirebaseDatabase firebaseDatabase,
|
* @param context
|
||||||
final int refId,
|
* @param app
|
||||||
final String path,
|
* @param id
|
||||||
final ReadableArray modifiersArray) {
|
* @param refPath
|
||||||
mPath = path;
|
* @param modifiersArray
|
||||||
mRefId = refId;
|
*/
|
||||||
mReactContext = context;
|
RNFirebaseDatabaseReference(ReactContext context, String app, int id, String refPath, ReadableArray modifiersArray) {
|
||||||
mChildEventListeners = new SparseArray<ChildEventListener>();
|
refId = id;
|
||||||
mValueEventListeners = new SparseArray<ValueEventListener>();
|
appName = app;
|
||||||
mQuery = this.buildDatabaseQueryAtPathAndModifiers(firebaseDatabase, path, modifiersArray);
|
path = refPath;
|
||||||
|
reactContext = context;
|
||||||
|
|
||||||
|
// todo only create if needed
|
||||||
|
childEventListeners = new SparseArray<ChildEventListener>();
|
||||||
|
valueEventListeners = new SparseArray<ValueEventListener>();
|
||||||
|
|
||||||
|
query = buildDatabaseQueryAtPathAndModifiers(path, modifiersArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for a single 'value' event from firebase.
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
void addOnceValueEventListener(final Promise promise) {
|
||||||
|
ValueEventListener onceValueEventListener = new ValueEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||||
|
WritableMap data = Utils.snapshotToMap("value", path, dataSnapshot, null, refId, 0);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled(DatabaseError error) {
|
||||||
|
RNFirebaseDatabase.handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
query.addListenerForSingleValueEvent(onceValueEventListener);
|
||||||
|
|
||||||
|
Log.d(TAG, "Added OnceValueEventListener for refId: " + refId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for single 'child_X' event from firebase.
|
||||||
|
* @param eventName
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
void addChildOnceEventListener(final String eventName, final Promise promise) {
|
||||||
|
ChildEventListener childEventListener = new ChildEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
||||||
|
if ("child_added".equals(eventName)) {
|
||||||
|
query.removeEventListener(this);
|
||||||
|
WritableMap data = Utils.snapshotToMap("child_added", path, dataSnapshot, previousChildName, refId, 0);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
|
||||||
|
if ("child_changed".equals(eventName)) {
|
||||||
|
query.removeEventListener(this);
|
||||||
|
WritableMap data = Utils.snapshotToMap("child_changed", path, dataSnapshot, previousChildName, refId, 0);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
||||||
|
if ("child_removed".equals(eventName)) {
|
||||||
|
query.removeEventListener(this);
|
||||||
|
WritableMap data = Utils.snapshotToMap("child_removed", path, dataSnapshot, null, refId, 0);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
|
||||||
|
if ("child_moved".equals(eventName)) {
|
||||||
|
query.removeEventListener(this);
|
||||||
|
WritableMap data = Utils.snapshotToMap("child_moved", path, dataSnapshot, previousChildName, refId, 0);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled(DatabaseError error) {
|
||||||
|
query.removeEventListener(this);
|
||||||
|
RNFirebaseDatabase.handlePromise(promise, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
query.addChildEventListener(childEventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// todo cleanup all below
|
||||||
|
|
||||||
void addChildEventListener(final int listenerId, final String eventName) {
|
void addChildEventListener(final int listenerId, final String eventName) {
|
||||||
if (mChildEventListeners.get(listenerId) != null) {
|
if (childEventListeners.get(listenerId) != null) {
|
||||||
ChildEventListener childEventListener = new ChildEventListener() {
|
ChildEventListener childEventListener = new ChildEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
||||||
|
@ -83,16 +180,16 @@ public class RNFirebaseDatabaseReference {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mChildEventListeners.put(listenerId, childEventListener);
|
childEventListeners.put(listenerId, childEventListener);
|
||||||
mQuery.addChildEventListener(childEventListener);
|
query.addChildEventListener(childEventListener);
|
||||||
Log.d(TAG, "Added ChildEventListener for refId: " + mRefId + " listenerId: " + listenerId);
|
Log.d(TAG, "Added ChildEventListener for refId: " + refId + " listenerId: " + listenerId);
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "ChildEventListener for refId: " + mRefId + " listenerId: " + listenerId + " already exists");
|
Log.d(TAG, "ChildEventListener for refId: " + refId + " listenerId: " + listenerId + " already exists");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addValueEventListener(final int listenerId) {
|
void addValueEventListener(final int listenerId) {
|
||||||
if (mValueEventListeners.get(listenerId) != null) {
|
if (valueEventListeners.get(listenerId) != null) {
|
||||||
ValueEventListener valueEventListener = new ValueEventListener() {
|
ValueEventListener valueEventListener = new ValueEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||||
|
@ -106,91 +203,15 @@ public class RNFirebaseDatabaseReference {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mValueEventListeners.put(listenerId, valueEventListener);
|
valueEventListeners.put(listenerId, valueEventListener);
|
||||||
mQuery.addValueEventListener(valueEventListener);
|
query.addValueEventListener(valueEventListener);
|
||||||
Log.d(TAG, "Added ValueEventListener for refId: " + mRefId + " listenerId: " + listenerId);
|
Log.d(TAG, "Added ValueEventListener for refId: " + refId + " listenerId: " + listenerId);
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "ValueEventListener for refId: " + mRefId + " listenerId: " + listenerId + " already exists");
|
Log.d(TAG, "ValueEventListener for refId: " + refId + " listenerId: " + listenerId + " already exists");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addOnceValueEventListener(final Callback callback) {
|
|
||||||
final ValueEventListener onceValueEventListener = new ValueEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
|
||||||
WritableMap data = Utils.snapshotToMap("value", mRefId, null, mPath, dataSnapshot, null);
|
|
||||||
callback.invoke(null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancelled(DatabaseError error) {
|
|
||||||
WritableMap err = Arguments.createMap();
|
|
||||||
err.putInt("refId", mRefId);
|
|
||||||
err.putString("path", mPath);
|
|
||||||
err.putInt("code", error.getCode());
|
|
||||||
err.putString("details", error.getDetails());
|
|
||||||
err.putString("message", error.getMessage());
|
|
||||||
callback.invoke(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mQuery.addListenerForSingleValueEvent(onceValueEventListener);
|
|
||||||
Log.d(TAG, "Added OnceValueEventListener for refId: " + mRefId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addChildOnceEventListener(final String eventName, final Callback callback) {
|
|
||||||
ChildEventListener childEventListener = new ChildEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
|
||||||
if ("child_added".equals(eventName)) {
|
|
||||||
mQuery.removeEventListener(this);
|
|
||||||
WritableMap data = Utils.snapshotToMap("child_added", mRefId, null, mPath, dataSnapshot, previousChildName);
|
|
||||||
callback.invoke(null, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
|
|
||||||
if ("child_changed".equals(eventName)) {
|
|
||||||
mQuery.removeEventListener(this);
|
|
||||||
WritableMap data = Utils.snapshotToMap("child_changed", mRefId, null, mPath, dataSnapshot, previousChildName);
|
|
||||||
callback.invoke(null, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
|
||||||
if ("child_removed".equals(eventName)) {
|
|
||||||
mQuery.removeEventListener(this);
|
|
||||||
WritableMap data = Utils.snapshotToMap("child_removed", mRefId, null, mPath, dataSnapshot, null);
|
|
||||||
callback.invoke(null, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
|
|
||||||
if ("child_moved".equals(eventName)) {
|
|
||||||
mQuery.removeEventListener(this);
|
|
||||||
WritableMap data = Utils.snapshotToMap("child_moved", mRefId, null, mPath, dataSnapshot, previousChildName);
|
|
||||||
callback.invoke(null, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancelled(DatabaseError error) {
|
|
||||||
mQuery.removeEventListener(this);
|
|
||||||
WritableMap err = Arguments.createMap();
|
|
||||||
err.putInt("refId", mRefId);
|
|
||||||
err.putString("path", mPath);
|
|
||||||
err.putInt("code", error.getCode());
|
|
||||||
err.putString("details", error.getDetails());
|
|
||||||
err.putString("message", error.getMessage());
|
|
||||||
callback.invoke(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mQuery.addChildEventListener(childEventListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeEventListener(int listenerId, String eventName) {
|
void removeEventListener(int listenerId, String eventName) {
|
||||||
if ("value".equals(eventName)) {
|
if ("value".equals(eventName)) {
|
||||||
|
@ -201,7 +222,7 @@ public class RNFirebaseDatabaseReference {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasListeners() {
|
boolean hasListeners() {
|
||||||
return mChildEventListeners.size() > 0 || mValueEventListeners.size() > 0;
|
return childEventListeners.size() > 0 || valueEventListeners.size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
@ -211,51 +232,52 @@ public class RNFirebaseDatabaseReference {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeChildEventListener(Integer listenerId) {
|
private void removeChildEventListener(Integer listenerId) {
|
||||||
ChildEventListener listener = mChildEventListeners.get(listenerId);
|
ChildEventListener listener = childEventListeners.get(listenerId);
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
mQuery.removeEventListener(listener);
|
query.removeEventListener(listener);
|
||||||
mChildEventListeners.delete(listenerId);
|
childEventListeners.delete(listenerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeValueEventListener(Integer listenerId) {
|
private void removeValueEventListener(Integer listenerId) {
|
||||||
ValueEventListener listener = mValueEventListeners.get(listenerId);
|
ValueEventListener listener = valueEventListeners.get(listenerId);
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
mQuery.removeEventListener(listener);
|
query.removeEventListener(listener);
|
||||||
mValueEventListeners.delete(listenerId);
|
valueEventListeners.delete(listenerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDatabaseEvent(final String name, final Integer listenerId, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
private void handleDatabaseEvent(final String name, final Integer listenerId, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
||||||
WritableMap data = Utils.snapshotToMap(name, mRefId, listenerId, mPath, dataSnapshot, previousChildName);
|
WritableMap data = Utils.snapshotToMap(name, path, dataSnapshot, previousChildName, refId, listenerId);
|
||||||
WritableMap evt = Arguments.createMap();
|
WritableMap evt = Arguments.createMap();
|
||||||
evt.putString("eventName", name);
|
evt.putString("eventName", name);
|
||||||
evt.putMap("body", data);
|
evt.putMap("body", data);
|
||||||
|
|
||||||
Utils.sendEvent(mReactContext, "database_event", evt);
|
Utils.sendEvent(reactContext, "database_event", evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDatabaseError(final Integer listenerId, final DatabaseError error) {
|
private void handleDatabaseError(final Integer listenerId, final DatabaseError error) {
|
||||||
WritableMap errMap = Arguments.createMap();
|
WritableMap errMap = Arguments.createMap();
|
||||||
|
|
||||||
errMap.putInt("refId", mRefId);
|
errMap.putInt("refId", refId);
|
||||||
if (listenerId != null) {
|
if (listenerId != null) {
|
||||||
errMap.putInt("listenerId", listenerId);
|
errMap.putInt("listenerId", listenerId);
|
||||||
}
|
}
|
||||||
errMap.putString("path", mPath);
|
errMap.putString("path", path);
|
||||||
errMap.putInt("code", error.getCode());
|
errMap.putInt("code", error.getCode());
|
||||||
errMap.putString("details", error.getDetails());
|
errMap.putString("details", error.getDetails());
|
||||||
errMap.putString("message", error.getMessage());
|
errMap.putString("message", error.getMessage());
|
||||||
|
|
||||||
Utils.sendEvent(mReactContext, "database_error", errMap);
|
Utils.sendEvent(reactContext, "database_error", errMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Query buildDatabaseQueryAtPathAndModifiers(final FirebaseDatabase firebaseDatabase,
|
private Query buildDatabaseQueryAtPathAndModifiers(String path, ReadableArray modifiers) {
|
||||||
final String path,
|
FirebaseDatabase firebaseDatabase = RNFirebaseDatabase.getDatabaseForApp(appName);
|
||||||
final ReadableArray modifiers) {
|
|
||||||
Query query = firebaseDatabase.getReference(path);
|
Query query = firebaseDatabase.getReference(path);
|
||||||
List<Object> modifiersList = Utils.recursivelyDeconstructReadableArray(modifiers);
|
List<Object> modifiersList = Utils.recursivelyDeconstructReadableArray(modifiers);
|
||||||
|
|
||||||
|
// todo cleanup into utils
|
||||||
for (Object m : modifiersList) {
|
for (Object m : modifiersList) {
|
||||||
Map<String, Object> modifier = (Map) m;
|
Map<String, Object> modifier = (Map) m;
|
||||||
String type = (String) modifier.get("type");
|
String type = (String) modifier.get("type");
|
||||||
|
|
|
@ -1,22 +1,39 @@
|
||||||
package io.invertase.firebase.database;
|
package io.invertase.firebase.database;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import com.facebook.react.bridge.WritableNativeArray;
|
||||||
|
import com.google.firebase.database.DataSnapshot;
|
||||||
|
import com.google.firebase.database.DatabaseError;
|
||||||
|
import com.google.firebase.database.MutableData;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import io.invertase.firebase.Utils;
|
||||||
|
|
||||||
public class RNFirebaseTransactionHandler {
|
public class RNFirebaseTransactionHandler {
|
||||||
|
private int transactionId;
|
||||||
|
private String appName;
|
||||||
private final ReentrantLock lock;
|
private final ReentrantLock lock;
|
||||||
private final Condition condition;
|
private final Condition condition;
|
||||||
private Map<String, Object> data;
|
private Map<String, Object> data;
|
||||||
private volatile boolean isReady;
|
private boolean signalled;
|
||||||
|
|
||||||
public Object value;
|
public Object value;
|
||||||
public boolean interrupted;
|
boolean interrupted;
|
||||||
public boolean abort = false;
|
boolean abort = false;
|
||||||
|
boolean timeout = false;
|
||||||
|
|
||||||
RNFirebaseTransactionHandler() {
|
RNFirebaseTransactionHandler(int id, String app) {
|
||||||
|
appName = app;
|
||||||
|
transactionId = id;
|
||||||
lock = new ReentrantLock();
|
lock = new ReentrantLock();
|
||||||
condition = lock.newCondition();
|
condition = lock.newCondition();
|
||||||
}
|
}
|
||||||
|
@ -24,19 +41,22 @@ public class RNFirebaseTransactionHandler {
|
||||||
/**
|
/**
|
||||||
* Signal that the transaction data has been received
|
* Signal that the transaction data has been received
|
||||||
*
|
*
|
||||||
* @param updateData
|
* @param updates
|
||||||
*/
|
*/
|
||||||
public void signalUpdateReceived(Map<String, Object> updateData) {
|
void signalUpdateReceived(ReadableMap updates) {
|
||||||
lock.lock();
|
Map<String, Object> updateData = Utils.recursivelyDeconstructReadableMap(updates);
|
||||||
|
|
||||||
abort = (Boolean) updateData.get("abort");
|
lock.lock();
|
||||||
value = updateData.get("value");
|
value = updateData.get("value");
|
||||||
|
abort = (Boolean) updateData.get("abort");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isReady)
|
if (signalled) {
|
||||||
throw new IllegalStateException("This transactionUpdateCallback has already been called.");
|
throw new IllegalStateException("This transactionUpdateHandler has already been signalled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
signalled = true;
|
||||||
data = updateData;
|
data = updateData;
|
||||||
isReady = true;
|
|
||||||
condition.signalAll();
|
condition.signalAll();
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
@ -44,16 +64,20 @@ public class RNFirebaseTransactionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for transactionUpdateReceived to signal condition
|
* Wait for signalUpdateReceived to signal condition
|
||||||
|
*
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
void await() throws InterruptedException {
|
void await() throws InterruptedException {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
Boolean notTimedOut = false;
|
|
||||||
|
long timeoutExpired = System.currentTimeMillis() + 5000;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (!notTimedOut && !isReady) {
|
while (!timeout && !condition.await(250, TimeUnit.MILLISECONDS) && !signalled) {
|
||||||
notTimedOut = condition.await(30, TimeUnit.SECONDS);
|
if (!signalled && System.currentTimeMillis() > timeoutExpired) {
|
||||||
|
timeout = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
@ -62,9 +86,68 @@ public class RNFirebaseTransactionHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the
|
* Get the
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
Map<String, Object> getUpdates() {
|
Map<String, Object> getUpdates() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a RN map of transaction mutable data for sending to js
|
||||||
|
*
|
||||||
|
* @param updatesData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
WritableMap createUpdateMap(MutableData updatesData) {
|
||||||
|
final WritableMap updatesMap = Arguments.createMap();
|
||||||
|
|
||||||
|
updatesMap.putInt("id", transactionId);
|
||||||
|
updatesMap.putString("type", "update");
|
||||||
|
|
||||||
|
// all events get distributed js side based on app name
|
||||||
|
updatesMap.putString("appName", appName);
|
||||||
|
|
||||||
|
if (!updatesData.hasChildren()) {
|
||||||
|
Utils.mapPutValue("value", updatesData.getValue(), updatesMap);
|
||||||
|
} else {
|
||||||
|
Object value = Utils.castValue(updatesData);
|
||||||
|
|
||||||
|
if (value instanceof WritableNativeArray) {
|
||||||
|
updatesMap.putArray("value", (WritableArray) value);
|
||||||
|
} else {
|
||||||
|
updatesMap.putMap("value", (WritableMap) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WritableMap createResultMap(@Nullable DatabaseError error, boolean committed, DataSnapshot snapshot) {
|
||||||
|
WritableMap resultMap = Arguments.createMap();
|
||||||
|
|
||||||
|
resultMap.putInt("id", transactionId);
|
||||||
|
resultMap.putString("appName", appName);
|
||||||
|
|
||||||
|
resultMap.putBoolean("timeout", timeout);
|
||||||
|
resultMap.putBoolean("committed", committed);
|
||||||
|
resultMap.putBoolean("interrupted", interrupted);
|
||||||
|
|
||||||
|
if (error != null || timeout || interrupted) {
|
||||||
|
resultMap.putString("type", "error");
|
||||||
|
if (error != null) resultMap.putMap("error", RNFirebaseDatabase.getJSError(error));
|
||||||
|
if (error == null && timeout) {
|
||||||
|
WritableMap timeoutError = Arguments.createMap();
|
||||||
|
timeoutError.putString("code", "DATABASE/INTERNAL-TIMEOUT");
|
||||||
|
timeoutError.putString("message", "A timeout occurred whilst waiting for RN JS thread to send transaction updates.");
|
||||||
|
resultMap.putMap("error", timeoutError);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultMap.putString("type", "complete");
|
||||||
|
resultMap.putMap("snapshot", Utils.snapshotToMap(snapshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -777,7 +777,7 @@ RCT_EXPORT_METHOD(fetchProvidersForEmail:
|
||||||
} else if ([provider compare:@"google" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
} else if ([provider compare:@"google" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||||
credential = [FIRGoogleAuthProvider credentialWithIDToken:authToken accessToken:authTokenSecret];
|
credential = [FIRGoogleAuthProvider credentialWithIDToken:authToken accessToken:authTokenSecret];
|
||||||
} else if ([provider compare:@"password" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
} else if ([provider compare:@"password" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||||
credential = [FIREmailPasswordAuthProvider credentialWithEmail:authToken password:authTokenSecret];
|
credential = [FIREmailAuthProvider credentialWithEmail:authToken password:authTokenSecret];
|
||||||
} else if ([provider compare:@"github" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
} else if ([provider compare:@"github" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||||
credential = [FIRGitHubAuthProvider credentialWithToken:authToken];
|
credential = [FIRGitHubAuthProvider credentialWithToken:authToken];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
import { promisify, typeOf } from './../../utils';
|
import { typeOf } from './../../utils';
|
||||||
import Reference from './reference';
|
import Reference from './reference';
|
||||||
|
|
||||||
const FirebaseDatabase = NativeModules.RNFirebaseDatabase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect
|
* @url https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect
|
||||||
|
@ -28,7 +27,7 @@ export default class Disconnect {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
set(value: string | Object) {
|
set(value: string | Object) {
|
||||||
return promisify('onDisconnectSet', FirebaseDatabase)(this.path, { type: typeOf(value), value });
|
return this.database._native.onDisconnectSet(this.path, { type: typeOf(value), value });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +36,7 @@ export default class Disconnect {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
update(values: Object) {
|
update(values: Object) {
|
||||||
return promisify('onDisconnectUpdate', FirebaseDatabase)(this.path, values);
|
return this.database._native.onDisconnectUpdate(this.path, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +44,7 @@ export default class Disconnect {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
remove() {
|
remove() {
|
||||||
return promisify('onDisconnectRemove', FirebaseDatabase)(this.path);
|
return this.database._native.onDisconnectRemove(this.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +52,6 @@ export default class Disconnect {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
cancel() {
|
cancel() {
|
||||||
return promisify('onDisconnectCancel', FirebaseDatabase)(this.path);
|
return this.database._native.onDisconnectCancel(this.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,45 +4,37 @@
|
||||||
*/
|
*/
|
||||||
import { NativeModules } from 'react-native';
|
import { NativeModules } from 'react-native';
|
||||||
|
|
||||||
import ModuleBase from './../../utils/ModuleBase';
|
|
||||||
import Snapshot from './snapshot';
|
|
||||||
import Reference from './reference';
|
import Reference from './reference';
|
||||||
import TransactionHandler from './transaction';
|
import TransactionHandler from './transaction';
|
||||||
import { promisify } from './../../utils';
|
import ModuleBase from './../../utils/ModuleBase';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Database
|
* @class Database
|
||||||
*/
|
*/
|
||||||
// TODO refactor native and js - legacy code here using old fb methods
|
|
||||||
export default class Database extends ModuleBase {
|
export default class Database extends ModuleBase {
|
||||||
constructor(firebaseApp: Object, options: Object = {}) {
|
constructor(firebaseApp: Object, options: Object = {}) {
|
||||||
super(firebaseApp, options, 'Database', true);
|
super(firebaseApp, options, 'Database', true);
|
||||||
this.references = {};
|
this._transactionHandler = new TransactionHandler(this);
|
||||||
this.serverTimeOffset = 0;
|
if (this._options.persistence) this._native.setPersistence(this._options.persistence);
|
||||||
this.persistenceEnabled = false;
|
|
||||||
this.transaction = new TransactionHandler(this);
|
|
||||||
|
|
||||||
if (options.persistence === true) {
|
// todo event & error listeners
|
||||||
this._setPersistence(true);
|
// todo serverTimeOffset event/listener - make ref natively and switch to events
|
||||||
}
|
// todo use nativeToJSError for on/off error events
|
||||||
|
}
|
||||||
|
|
||||||
this.successListener = this._eventEmitter.addListener(
|
/**
|
||||||
'database_event',
|
*
|
||||||
event => this._handleDatabaseEvent(event),
|
*/
|
||||||
);
|
goOnline() {
|
||||||
|
this._native.goOnline();
|
||||||
|
}
|
||||||
|
|
||||||
this.errorListener = this._eventEmitter.addListener(
|
/**
|
||||||
'database_error',
|
*
|
||||||
err => this._handleDatabaseError(err),
|
*/
|
||||||
);
|
goOffline() {
|
||||||
|
this._native.goOffline();
|
||||||
this.offsetRef = this.ref('.info/serverTimeOffset');
|
|
||||||
|
|
||||||
this.offsetRef.on('value', (snapshot) => {
|
|
||||||
this.serverTimeOffset = snapshot.val() || this.serverTimeOffset;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.log.debug('Created new Database instance', this.options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,146 +45,6 @@ export default class Database extends ModuleBase {
|
||||||
ref(path: string) {
|
ref(path: string) {
|
||||||
return new Reference(this, path);
|
return new Reference(this, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {*}
|
|
||||||
* @param ref
|
|
||||||
* @param listener
|
|
||||||
*/
|
|
||||||
on(ref: Reference, listener: DatabaseListener) {
|
|
||||||
const { refId, path, query } = ref;
|
|
||||||
const { listenerId, eventName } = listener;
|
|
||||||
this.log.debug('on() : ', ref.refId, listenerId, eventName);
|
|
||||||
this.references[refId] = ref;
|
|
||||||
return promisify('on', this._native)(refId, path, query.getModifiers(), listenerId, eventName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {*}
|
|
||||||
* @param refId
|
|
||||||
* @param listeners
|
|
||||||
* @param remainingListenersCount
|
|
||||||
*/
|
|
||||||
off(refId: number, listeners: Array<DatabaseListener>, remainingListenersCount: number) {
|
|
||||||
this.log.debug('off() : ', refId, listeners);
|
|
||||||
|
|
||||||
// Delete the reference if there are no more listeners
|
|
||||||
if (remainingListenersCount === 0) delete this.references[refId];
|
|
||||||
|
|
||||||
if (listeners.length === 0) return Promise.resolve();
|
|
||||||
|
|
||||||
return promisify('off', this._native)(refId, listeners.map(listener => ({
|
|
||||||
listenerId: listener.listenerId,
|
|
||||||
eventName: listener.eventName,
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all references and their native listeners
|
|
||||||
* @returns {Promise.<*>}
|
|
||||||
*/
|
|
||||||
cleanup() {
|
|
||||||
const promises = [];
|
|
||||||
Object.keys(this.references).forEach((refId) => {
|
|
||||||
const ref = this.references[refId];
|
|
||||||
promises.push(this.off(Number(refId), Object.values(ref.refListeners), 0));
|
|
||||||
});
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
goOnline() {
|
|
||||||
this._native.goOnline();
|
|
||||||
}
|
|
||||||
|
|
||||||
goOffline() {
|
|
||||||
this._native.goOffline();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNALS
|
|
||||||
*/
|
|
||||||
_getServerTime() {
|
|
||||||
return new Date().getTime() + this.serverTimeOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enabled / disable database persistence
|
|
||||||
* @param enable
|
|
||||||
* @returns {*}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_setPersistence(enable: boolean = true) {
|
|
||||||
if (this.persistenceEnabled !== enable) {
|
|
||||||
this.persistenceEnabled = enable;
|
|
||||||
this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence.`);
|
|
||||||
return promisify('enablePersistence', this._native)(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject({ status: 'Already enabled' });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleDatabaseEvent(event: Object) {
|
|
||||||
const body = event.body || {};
|
|
||||||
const { refId, listenerId, path, eventName, snapshot, previousChildName } = body;
|
|
||||||
this.log.debug('_handleDatabaseEvent: ', refId, listenerId, path, eventName, snapshot && snapshot.key);
|
|
||||||
if (this.references[refId] && this.references[refId].refListeners[listenerId]) {
|
|
||||||
const cb = this.references[refId].refListeners[listenerId].successCallback;
|
|
||||||
cb(new Snapshot(this.references[refId], snapshot), previousChildName);
|
|
||||||
} else {
|
|
||||||
this._native.off(refId, [{ listenerId, eventName }], () => {
|
|
||||||
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', refId, listenerId, eventName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an native error object to a 'firebase like' error.
|
|
||||||
* @param error
|
|
||||||
* @returns {Error}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_toFirebaseError(error) {
|
|
||||||
const { path, message, modifiers, code, details } = error;
|
|
||||||
let firebaseMessage = `FirebaseError: ${message.toLowerCase().replace(/\s/g, '_')}`;
|
|
||||||
|
|
||||||
if (path) {
|
|
||||||
firebaseMessage = `${firebaseMessage} at /${path}\r\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// $FlowFixMe
|
|
||||||
const firebaseError: FirebaseError = new Error(firebaseMessage);
|
|
||||||
|
|
||||||
firebaseError.code = code;
|
|
||||||
firebaseError.path = path;
|
|
||||||
firebaseError.details = details;
|
|
||||||
firebaseError.modifiers = modifiers;
|
|
||||||
|
|
||||||
return firebaseError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param error
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleDatabaseError(error: Object = {}) {
|
|
||||||
const { refId, listenerId, path } = error;
|
|
||||||
const firebaseError = this._toFirebaseError(error);
|
|
||||||
|
|
||||||
this.log.debug('_handleDatabaseError ->', refId, listenerId, path, 'database_error', error);
|
|
||||||
|
|
||||||
if (this.references[refId] && this.references[refId].refListeners[listenerId]) {
|
|
||||||
const failureCb = this.references[refId].refListeners[listenerId].failureCallback;
|
|
||||||
if (failureCb) failureCb(firebaseError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const statics = {
|
export const statics = {
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
/**
|
||||||
|
* @flow
|
||||||
|
* Database representation wrapper
|
||||||
|
*/
|
||||||
|
import { NativeModules } from 'react-native';
|
||||||
|
|
||||||
|
import ModuleBase from './../../utils/ModuleBase';
|
||||||
|
import Snapshot from './snapshot';
|
||||||
|
import Reference from './reference';
|
||||||
|
import TransactionHandler from './transaction';
|
||||||
|
import { promisify } from './../../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Database
|
||||||
|
*/
|
||||||
|
// TODO refactor native and js - legacy code here using old fb methods
|
||||||
|
export default class Database extends ModuleBase {
|
||||||
|
constructor(firebaseApp: Object, options: Object = {}) {
|
||||||
|
super(firebaseApp, options, 'Database', true);
|
||||||
|
this.references = {};
|
||||||
|
this.serverTimeOffset = 0;
|
||||||
|
this.persistenceEnabled = false;
|
||||||
|
this.transaction = new TransactionHandler(this);
|
||||||
|
|
||||||
|
if (options.persistence === true) {
|
||||||
|
this._setPersistence(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.successListener = this._eventEmitter.addListener(
|
||||||
|
'database_event',
|
||||||
|
event => this._handleDatabaseEvent(event),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.errorListener = this._eventEmitter.addListener(
|
||||||
|
'database_error',
|
||||||
|
err => this._handleDatabaseError(err),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.offsetRef = this.ref('.info/serverTimeOffset');
|
||||||
|
|
||||||
|
this.offsetRef.on('value', (snapshot) => {
|
||||||
|
this.serverTimeOffset = snapshot.val() || this.serverTimeOffset;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.log.debug('Created new Database instance', this.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO wtf is this doing here ;p
|
||||||
|
* @returns {*}
|
||||||
|
* @param ref
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
on(ref: Reference, listener: DatabaseListener) {
|
||||||
|
const { refId, path, query } = ref;
|
||||||
|
const { listenerId, eventName } = listener;
|
||||||
|
this.log.debug('on() : ', ref.refId, listenerId, eventName);
|
||||||
|
this.references[refId] = ref;
|
||||||
|
return promisify('on', this._native)(refId, path, query.getModifiers(), listenerId, eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
* @param refId
|
||||||
|
* @param listeners
|
||||||
|
* @param remainingListenersCount
|
||||||
|
*/
|
||||||
|
off(refId: number, listeners: Array<DatabaseListener>, remainingListenersCount: number) {
|
||||||
|
this.log.debug('off() : ', refId, listeners);
|
||||||
|
|
||||||
|
// Delete the reference if there are no more listeners
|
||||||
|
if (remainingListenersCount === 0) delete this.references[refId];
|
||||||
|
|
||||||
|
if (listeners.length === 0) return Promise.resolve();
|
||||||
|
|
||||||
|
return promisify('off', this._native)(refId, listeners.map(listener => ({
|
||||||
|
listenerId: listener.listenerId,
|
||||||
|
eventName: listener.eventName,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all references and their native listeners
|
||||||
|
* @returns {Promise.<*>}
|
||||||
|
*/
|
||||||
|
cleanup() {
|
||||||
|
const promises = [];
|
||||||
|
Object.keys(this.references).forEach((refId) => {
|
||||||
|
const ref = this.references[refId];
|
||||||
|
promises.push(this.off(Number(refId), Object.values(ref.refListeners), 0));
|
||||||
|
});
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
// goOnline() {
|
||||||
|
// this._native.goOnline();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// goOffline() {
|
||||||
|
// this._native.goOffline();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNALS
|
||||||
|
*/
|
||||||
|
_getServerTime() {
|
||||||
|
return new Date().getTime() + this.serverTimeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Enabled / disable database persistence
|
||||||
|
// * @param enable
|
||||||
|
// * @returns {*}
|
||||||
|
// * @private
|
||||||
|
// */
|
||||||
|
// _setPersistence(enable: boolean = true) {
|
||||||
|
// if (this.persistenceEnabled !== enable) {
|
||||||
|
// this.persistenceEnabled = enable;
|
||||||
|
// this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence.`);
|
||||||
|
// return promisify('enablePersistence', this._native)(enable);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return Promise.reject({ status: 'Already enabled' });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleDatabaseEvent(event: Object) {
|
||||||
|
const body = event.body || {};
|
||||||
|
const { refId, listenerId, path, eventName, snapshot, previousChildName } = body;
|
||||||
|
this.log.debug('_handleDatabaseEvent: ', refId, listenerId, path, eventName, snapshot && snapshot.key);
|
||||||
|
if (this.references[refId] && this.references[refId].refListeners[listenerId]) {
|
||||||
|
const cb = this.references[refId].refListeners[listenerId].successCallback;
|
||||||
|
cb(new Snapshot(this.references[refId], snapshot), previousChildName);
|
||||||
|
} else {
|
||||||
|
this._native.off(refId, [{ listenerId, eventName }], () => {
|
||||||
|
this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', refId, listenerId, eventName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an native error object to a 'firebase like' error.
|
||||||
|
* @param error
|
||||||
|
* @returns {Error}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_toFirebaseError(error) {
|
||||||
|
const { path, message, modifiers, code, details } = error;
|
||||||
|
let firebaseMessage = `FirebaseError: ${message.toLowerCase().replace(/\s/g, '_')}`;
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
firebaseMessage = `${firebaseMessage} at /${path}\r\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
const firebaseError: FirebaseError = new Error(firebaseMessage);
|
||||||
|
|
||||||
|
firebaseError.code = code;
|
||||||
|
firebaseError.path = path;
|
||||||
|
firebaseError.details = details;
|
||||||
|
firebaseError.modifiers = modifiers;
|
||||||
|
|
||||||
|
return firebaseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param error
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleDatabaseError(error: Object = {}) {
|
||||||
|
const { refId, listenerId, path } = error;
|
||||||
|
const firebaseError = this._toFirebaseError(error);
|
||||||
|
|
||||||
|
this.log.debug('_handleDatabaseError ->', refId, listenerId, path, 'database_error', error);
|
||||||
|
|
||||||
|
if (this.references[refId] && this.references[refId].refListeners[listenerId]) {
|
||||||
|
const failureCb = this.references[refId].refListeners[listenerId].failureCallback;
|
||||||
|
if (failureCb) failureCb(firebaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const statics = {
|
||||||
|
ServerValue: NativeModules.FirebaseDatabase ? {
|
||||||
|
TIMESTAMP: NativeModules.FirebaseDatabase.serverValueTimestamp || { '.sv': 'timestamp' },
|
||||||
|
} : {},
|
||||||
|
};
|
|
@ -5,7 +5,7 @@ import Query from './query.js';
|
||||||
import Snapshot from './snapshot';
|
import Snapshot from './snapshot';
|
||||||
import Disconnect from './disconnect';
|
import Disconnect from './disconnect';
|
||||||
import ReferenceBase from './../../utils/ReferenceBase';
|
import ReferenceBase from './../../utils/ReferenceBase';
|
||||||
import { promisify, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils';
|
import { isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils';
|
||||||
|
|
||||||
// Unique Reference ID for native events
|
// Unique Reference ID for native events
|
||||||
let refId = 1;
|
let refId = 1;
|
||||||
|
@ -57,7 +57,6 @@ export default class Reference extends ReferenceBase {
|
||||||
database: Object;
|
database: Object;
|
||||||
query: Query;
|
query: Query;
|
||||||
|
|
||||||
// todo logger missing as reference base no longer extends module base
|
|
||||||
constructor(database: Object, path: string, existingModifiers?: Array<DatabaseModifier>) {
|
constructor(database: Object, path: string, existingModifiers?: Array<DatabaseModifier>) {
|
||||||
super(path, database);
|
super(path, database);
|
||||||
this.refId = refId++;
|
this.refId = refId++;
|
||||||
|
@ -65,7 +64,7 @@ export default class Reference extends ReferenceBase {
|
||||||
this.database = database;
|
this.database = database;
|
||||||
this.namespace = 'firebase:db:ref';
|
this.namespace = 'firebase:db:ref';
|
||||||
this.query = new Query(this, path, existingModifiers);
|
this.query = new Query(this, path, existingModifiers);
|
||||||
this.log.debug('Created new Reference', this.refId, this.path);
|
// TODO this.log.debug('Created new Reference', this.refId, this.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,8 +73,7 @@ export default class Reference extends ReferenceBase {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
keepSynced(bool: boolean) {
|
keepSynced(bool: boolean) {
|
||||||
const path = this.path;
|
return this.database._native.keepSynced(this.path, bool);
|
||||||
return promisify('keepSynced', this.database._native)(path, bool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,9 +82,7 @@ export default class Reference extends ReferenceBase {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
set(value: any) {
|
set(value: any) {
|
||||||
const path = this.path;
|
return this.database._native.set(this.path, this._serializeAnyType(value));
|
||||||
const _value = this._serializeAnyType(value);
|
|
||||||
return promisify('set', this.database._native)(path, _value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,21 +91,20 @@ export default class Reference extends ReferenceBase {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
setPriority(priority: string | number | null) {
|
setPriority(priority: string | number | null) {
|
||||||
const path = this.path;
|
|
||||||
const _priority = this._serializeAnyType(priority);
|
const _priority = this._serializeAnyType(priority);
|
||||||
return promisify('priority', FirebaseDatabase)(path, _priority);
|
return this.database._native.setPriority(this.path, _priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
* @param value
|
||||||
* @param priority
|
* @param priority
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
setWithPriority(value: any, priority: string | number | null) {
|
setWithPriority(value: any, priority: string | number | null) {
|
||||||
const path = this.path;
|
|
||||||
const _priority = this._serializeAnyType(priority);
|
|
||||||
const _value = this._serializeAnyType(value);
|
const _value = this._serializeAnyType(value);
|
||||||
return promisify('withPriority', FirebaseDatabase)(path, _value, _priority);
|
const _priority = this._serializeAnyType(priority);
|
||||||
|
return this.database._native.setWithPriority(this.path, _value, _priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,9 +113,8 @@ export default class Reference extends ReferenceBase {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
update(val: Object) {
|
update(val: Object) {
|
||||||
const path = this.path;
|
|
||||||
const value = this._serializeObject(val);
|
const value = this._serializeObject(val);
|
||||||
return promisify('update', this.database._native)(path, value);
|
return this.database._native.update(this.path, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,261 +122,7 @@ export default class Reference extends ReferenceBase {
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
remove() {
|
remove() {
|
||||||
return promisify('remove', this.database._native)(this.path);
|
return this.database._native.remove(this.path);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* @param onComplete
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
push(value: any, onComplete: Function) {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return new Reference(this.database, `${this.path}/${generatePushID(this.database.serverTimeOffset)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = this.path;
|
|
||||||
const _value = this._serializeAnyType(value);
|
|
||||||
return promisify('push', this.database._native)(path, _value)
|
|
||||||
.then(({ ref }) => {
|
|
||||||
const newRef = new Reference(this.database, ref);
|
|
||||||
if (isFunction(onComplete)) return onComplete(null, newRef);
|
|
||||||
return newRef;
|
|
||||||
}).catch((e) => {
|
|
||||||
if (isFunction(onComplete)) return onComplete(e, null);
|
|
||||||
return e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* iOS: Called once with the initial data at the specified location and then once each
|
|
||||||
* time the data changes. It won't trigger until the entire contents have been
|
|
||||||
* synchronized.
|
|
||||||
*
|
|
||||||
* Android: (@link https://github.com/invertase/react-native-firebase/issues/92)
|
|
||||||
* - Array & number values: Called once with the initial data at the specified
|
|
||||||
* location and then twice each time the value changes.
|
|
||||||
* - Other data types: Called once with the initial data at the specified location
|
|
||||||
* and once each time the data type changes.
|
|
||||||
*
|
|
||||||
* @callback onValueCallback
|
|
||||||
* @param {!DataSnapshot} dataSnapshot - Snapshot representing data at the location
|
|
||||||
* specified by the current ref. If location has no data, .val() will return null.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once for each initial child at the specified location and then again
|
|
||||||
* every time a new child is added.
|
|
||||||
*
|
|
||||||
* @callback onChildAddedCallback
|
|
||||||
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data for the
|
|
||||||
* relevant child.
|
|
||||||
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
|
|
||||||
* of the previous sibling child by sort order, or null if it is the first child.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once every time a child is removed.
|
|
||||||
*
|
|
||||||
* A child will get removed when either:
|
|
||||||
* - remove() is explicitly called on a child or one of its ancestors
|
|
||||||
* - set(null) is called on that child or one of its ancestors
|
|
||||||
* - a child has all of its children removed
|
|
||||||
* - there is a query in effect which now filters out the child (because it's sort
|
|
||||||
* order changed or the max limit was hit)
|
|
||||||
*
|
|
||||||
* @callback onChildRemovedCallback
|
|
||||||
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the old data for
|
|
||||||
* the child that was removed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a child (or any of its descendants) changes.
|
|
||||||
*
|
|
||||||
* A single child_changed event may represent multiple changes to the child.
|
|
||||||
*
|
|
||||||
* @callback onChildChangedCallback
|
|
||||||
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting new child contents.
|
|
||||||
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
|
|
||||||
* of the previous sibling child by sort order, or null if it is the first child.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a child's sort order changes, i.e. its position relative to its
|
|
||||||
* siblings changes.
|
|
||||||
*
|
|
||||||
* @callback onChildMovedCallback
|
|
||||||
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data of the moved
|
|
||||||
* child.
|
|
||||||
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
|
|
||||||
* of the previous sibling child by sort order, or null if it is the first child.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef (onValueCallback|onChildAddedCallback|onChildRemovedCallback|onChildChangedCallback|onChildMovedCallback) ReferenceEventCallback
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if the event subscription is cancelled because the client does
|
|
||||||
* not have permission to read this data (or has lost the permission to do so).
|
|
||||||
*
|
|
||||||
* @callback onFailureCallback
|
|
||||||
* @param {Error} error - Object indicating why the failure occurred
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds callback handlers to when data changes at the current ref's location.
|
|
||||||
* The primary method of reading data from a Database.
|
|
||||||
*
|
|
||||||
* Callbacks can be unbound using {@link off}.
|
|
||||||
*
|
|
||||||
* Event Types:
|
|
||||||
*
|
|
||||||
* - value: {@link onValueCallback}.
|
|
||||||
* - child_added: {@link onChildAddedCallback}
|
|
||||||
* - child_removed: {@link onChildRemovedCallback}
|
|
||||||
* - child_changed: {@link onChildChangedCallback}
|
|
||||||
* - child_moved: {@link onChildMovedCallback}
|
|
||||||
*
|
|
||||||
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
|
|
||||||
* @param {ReferenceEventCallback} successCallback - Function that will be called
|
|
||||||
* when the event occurs with the new data.
|
|
||||||
* @param {onFailureCallback=} failureCallbackOrContext - Optional callback that is called
|
|
||||||
* if the event subscription fails. {@link onFailureCallback}
|
|
||||||
* @param {*=} context - Optional object to bind the callbacks to when calling them.
|
|
||||||
* @returns {ReferenceEventCallback} callback function, unmodified (unbound), for
|
|
||||||
* convenience if you want to pass an inline function to on() and store it later for
|
|
||||||
* removing using off().
|
|
||||||
*
|
|
||||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on}
|
|
||||||
*/
|
|
||||||
on(eventType: string, successCallback: () => any, failureCallbackOrContext: () => any, context: any) {
|
|
||||||
if (!eventType) throw new Error('Error: Query on failed: Was called with 0 arguments. Expects at least 2');
|
|
||||||
if (!ReferenceEventTypes[eventType]) throw new Error('Query.on failed: First argument must be a valid event type: "value", "child_added", "child_removed", "child_changed", or "child_moved".');
|
|
||||||
if (!successCallback) throw new Error('Query.on failed: Was called with 1 argument. Expects at least 2.');
|
|
||||||
if (!isFunction(successCallback)) throw new Error('Query.on failed: Second argument must be a valid function.');
|
|
||||||
if (arguments.length > 2 && !failureCallbackOrContext) throw new Error('Query.on failed: third argument must either be a cancel callback or a context object.');
|
|
||||||
|
|
||||||
this.log.debug('adding reference.on', this.refId, eventType);
|
|
||||||
let _failureCallback;
|
|
||||||
let _context;
|
|
||||||
|
|
||||||
if (context) {
|
|
||||||
_context = context;
|
|
||||||
_failureCallback = failureCallbackOrContext;
|
|
||||||
} else if (isFunction(failureCallbackOrContext)) {
|
|
||||||
_failureCallback = failureCallbackOrContext;
|
|
||||||
} else {
|
|
||||||
_context = failureCallbackOrContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_failureCallback) {
|
|
||||||
_failureCallback = (error) => {
|
|
||||||
if (error.message.startsWith('FirebaseError: permission_denied')) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
error.message = `permission_denied at /${this.path}: Client doesn't have permission to access the desired data.`
|
|
||||||
}
|
|
||||||
|
|
||||||
failureCallbackOrContext(error);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let _successCallback;
|
|
||||||
|
|
||||||
if (_context) {
|
|
||||||
_successCallback = successCallback.bind(_context);
|
|
||||||
} else {
|
|
||||||
_successCallback = successCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
const listener = {
|
|
||||||
listenerId: Object.keys(this.refListeners).length + 1,
|
|
||||||
eventName: eventType,
|
|
||||||
successCallback: _successCallback,
|
|
||||||
failureCallback: _failureCallback,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.refListeners[listener.listenerId] = listener;
|
|
||||||
this.database.on(this, listener);
|
|
||||||
return successCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param eventName
|
|
||||||
* @param successCallback
|
|
||||||
* @param failureCallback
|
|
||||||
* TODO @param context
|
|
||||||
* @returns {Promise.<TResult>}
|
|
||||||
*/
|
|
||||||
once(eventName: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: FirebaseError) => void) {
|
|
||||||
return promisify('once', this.database._native)(this.refId, this.path, this.query.getModifiers(), eventName)
|
|
||||||
.then(({ snapshot }) => new Snapshot(this, snapshot))
|
|
||||||
.then((snapshot) => {
|
|
||||||
if (isFunction(successCallback)) successCallback(snapshot);
|
|
||||||
return snapshot;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
const firebaseError = this.database._toFirebaseError(error);
|
|
||||||
if (isFunction(failureCallback)) return failureCallback(firebaseError);
|
|
||||||
return Promise.reject(firebaseError);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches a callback attached with on().
|
|
||||||
*
|
|
||||||
* Calling off() on a parent listener will not automatically remove listeners
|
|
||||||
* registered on child nodes.
|
|
||||||
*
|
|
||||||
* If on() was called multiple times with the same eventType off() must be
|
|
||||||
* called multiple times to completely remove it.
|
|
||||||
*
|
|
||||||
* If a callback is not specified, all callbacks for the specified eventType
|
|
||||||
* will be removed. If no eventType or callback is specified, all callbacks
|
|
||||||
* for the Reference will be removed.
|
|
||||||
*
|
|
||||||
* If a context is specified, it too is used as a filter parameter: a callback
|
|
||||||
* will only be detached if, when it was attached with on(), the same event type,
|
|
||||||
* callback function and context were provided.
|
|
||||||
*
|
|
||||||
* If no callbacks matching the parameters provided are found, no callbacks are
|
|
||||||
* detached.
|
|
||||||
*
|
|
||||||
* @param {('value'|'child_added'|'child_changed'|'child_removed'|'child_moved')=} eventType - Type of event to detach callback for.
|
|
||||||
* @param {Function=} originalCallback - Original callback passed to on()
|
|
||||||
* TODO @param {*=} context - The context passed to on() when the callback was bound
|
|
||||||
*
|
|
||||||
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#off}
|
|
||||||
*/
|
|
||||||
off(eventType?: string = '', originalCallback?: () => any) {
|
|
||||||
this.log.debug('ref.off(): ', this.refId, eventType);
|
|
||||||
// $FlowFixMe
|
|
||||||
const listeners: Array<DatabaseListener> = Object.values(this.refListeners);
|
|
||||||
let listenersToRemove;
|
|
||||||
if (eventType && originalCallback) {
|
|
||||||
listenersToRemove = listeners.filter((listener) => {
|
|
||||||
return listener.eventName === eventType && listener.successCallback === originalCallback;
|
|
||||||
});
|
|
||||||
// Only remove a single listener as per the web spec
|
|
||||||
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]];
|
|
||||||
} else if (eventType) {
|
|
||||||
listenersToRemove = listeners.filter((listener) => {
|
|
||||||
return listener.eventName === eventType;
|
|
||||||
});
|
|
||||||
} else if (originalCallback) {
|
|
||||||
listenersToRemove = listeners.filter((listener) => {
|
|
||||||
return listener.successCallback === originalCallback;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
listenersToRemove = listeners;
|
|
||||||
}
|
|
||||||
// Remove the listeners from the reference to prevent memory leaks
|
|
||||||
listenersToRemove.forEach((listener) => {
|
|
||||||
delete this.refListeners[listener.listenerId];
|
|
||||||
});
|
|
||||||
return this.database.off(this.refId, listenersToRemove, Object.keys(this.refListeners).length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -418,10 +158,57 @@ export default class Reference extends ReferenceBase {
|
||||||
return resolve({ committed, snapshot });
|
return resolve({ committed, snapshot });
|
||||||
};
|
};
|
||||||
|
|
||||||
this.database.transaction.add(this, transactionUpdate, onCompleteWrapper, applyLocally);
|
this.database._transactionHandler.add(this, transactionUpdate, onCompleteWrapper, applyLocally);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param eventName
|
||||||
|
* @param successCallback
|
||||||
|
* @param failureCallback
|
||||||
|
* TODO @param context
|
||||||
|
* @returns {Promise.<any>}
|
||||||
|
*/
|
||||||
|
once(eventName: string = 'value', successCallback: (snapshot: Object) => void, failureCallback: (error: FirebaseError) => void) {
|
||||||
|
return this.database._native.once(this.refId, this.path, this.query.getModifiers(), eventName)
|
||||||
|
.then(({ snapshot }) => new Snapshot(this, snapshot))
|
||||||
|
.then((snapshot) => {
|
||||||
|
if (isFunction(successCallback)) successCallback(snapshot);
|
||||||
|
return snapshot;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (isFunction(failureCallback)) return failureCallback(error);
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param onComplete
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
push(value: any, onComplete: Function) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return new Reference(this.database, `${this.path}/${generatePushID(this.database.serverTimeOffset)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRef = new Reference(this.database, `${this.path}/${generatePushID(this.database.serverTimeOffset)}`);
|
||||||
|
const promise = newRef.set(value);
|
||||||
|
|
||||||
|
// todo 'ThenableReference'
|
||||||
|
return promise
|
||||||
|
.then(() => {
|
||||||
|
if (isFunction(onComplete)) return onComplete(null, newRef);
|
||||||
|
return newRef;
|
||||||
|
}).catch((error) => {
|
||||||
|
if (isFunction(onComplete)) return onComplete(error, null);
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MODIFIERS
|
* MODIFIERS
|
||||||
*/
|
*/
|
||||||
|
@ -665,4 +452,224 @@ export default class Reference extends ReferenceBase {
|
||||||
value,
|
value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
// todo below methods need refactoring
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iOS: Called once with the initial data at the specified location and then once each
|
||||||
|
* time the data changes. It won't trigger until the entire contents have been
|
||||||
|
* synchronized.
|
||||||
|
*
|
||||||
|
* Android: (@link https://github.com/invertase/react-native-firebase/issues/92)
|
||||||
|
* - Array & number values: Called once with the initial data at the specified
|
||||||
|
* location and then twice each time the value changes.
|
||||||
|
* - Other data types: Called once with the initial data at the specified location
|
||||||
|
* and once each time the data type changes.
|
||||||
|
*
|
||||||
|
* @callback onValueCallback
|
||||||
|
* @param {!DataSnapshot} dataSnapshot - Snapshot representing data at the location
|
||||||
|
* specified by the current ref. If location has no data, .val() will return null.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once for each initial child at the specified location and then again
|
||||||
|
* every time a new child is added.
|
||||||
|
*
|
||||||
|
* @callback onChildAddedCallback
|
||||||
|
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data for the
|
||||||
|
* relevant child.
|
||||||
|
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
|
||||||
|
* of the previous sibling child by sort order, or null if it is the first child.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once every time a child is removed.
|
||||||
|
*
|
||||||
|
* A child will get removed when either:
|
||||||
|
* - remove() is explicitly called on a child or one of its ancestors
|
||||||
|
* - set(null) is called on that child or one of its ancestors
|
||||||
|
* - a child has all of its children removed
|
||||||
|
* - there is a query in effect which now filters out the child (because it's sort
|
||||||
|
* order changed or the max limit was hit)
|
||||||
|
*
|
||||||
|
* @callback onChildRemovedCallback
|
||||||
|
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the old data for
|
||||||
|
* the child that was removed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a child (or any of its descendants) changes.
|
||||||
|
*
|
||||||
|
* A single child_changed event may represent multiple changes to the child.
|
||||||
|
*
|
||||||
|
* @callback onChildChangedCallback
|
||||||
|
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting new child contents.
|
||||||
|
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
|
||||||
|
* of the previous sibling child by sort order, or null if it is the first child.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a child's sort order changes, i.e. its position relative to its
|
||||||
|
* siblings changes.
|
||||||
|
*
|
||||||
|
* @callback onChildMovedCallback
|
||||||
|
* @param {!DataSnapshot} dataSnapshot - Snapshot reflecting the data of the moved
|
||||||
|
* child.
|
||||||
|
* @param {?ReferenceKey} previousChildKey - For ordering purposes, the key
|
||||||
|
* of the previous sibling child by sort order, or null if it is the first child.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef (onValueCallback|onChildAddedCallback|onChildRemovedCallback|onChildChangedCallback|onChildMovedCallback) ReferenceEventCallback
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if the event subscription is cancelled because the client does
|
||||||
|
* not have permission to read this data (or has lost the permission to do so).
|
||||||
|
*
|
||||||
|
* @callback onFailureCallback
|
||||||
|
* @param {Error} error - Object indicating why the failure occurred
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds callback handlers to when data changes at the current ref's location.
|
||||||
|
* The primary method of reading data from a Database.
|
||||||
|
*
|
||||||
|
* Callbacks can be unbound using {@link off}.
|
||||||
|
*
|
||||||
|
* Event Types:
|
||||||
|
*
|
||||||
|
* - value: {@link onValueCallback}.
|
||||||
|
* - child_added: {@link onChildAddedCallback}
|
||||||
|
* - child_removed: {@link onChildRemovedCallback}
|
||||||
|
* - child_changed: {@link onChildChangedCallback}
|
||||||
|
* - child_moved: {@link onChildMovedCallback}
|
||||||
|
*
|
||||||
|
* @param {ReferenceEventType} eventType - Type of event to attach a callback for.
|
||||||
|
* @param {ReferenceEventCallback} successCallback - Function that will be called
|
||||||
|
* when the event occurs with the new data.
|
||||||
|
* @param {onFailureCallback=} failureCallbackOrContext - Optional callback that is called
|
||||||
|
* if the event subscription fails. {@link onFailureCallback}
|
||||||
|
* @param {*=} context - Optional object to bind the callbacks to when calling them.
|
||||||
|
* @returns {ReferenceEventCallback} callback function, unmodified (unbound), for
|
||||||
|
* convenience if you want to pass an inline function to on() and store it later for
|
||||||
|
* removing using off().
|
||||||
|
*
|
||||||
|
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#on}
|
||||||
|
*/
|
||||||
|
on(eventType: string, successCallback: () => any, failureCallbackOrContext: () => any, context: any) {
|
||||||
|
if (!eventType) throw new Error('Error: Query on failed: Was called with 0 arguments. Expects at least 2');
|
||||||
|
if (!ReferenceEventTypes[eventType]) throw new Error('Query.on failed: First argument must be a valid event type: "value", "child_added", "child_removed", "child_changed", or "child_moved".');
|
||||||
|
if (!successCallback) throw new Error('Query.on failed: Was called with 1 argument. Expects at least 2.');
|
||||||
|
if (!isFunction(successCallback)) throw new Error('Query.on failed: Second argument must be a valid function.');
|
||||||
|
if (arguments.length > 2 && !failureCallbackOrContext) throw new Error('Query.on failed: third argument must either be a cancel callback or a context object.');
|
||||||
|
|
||||||
|
// TODO this.log.debug('adding reference.on', this.refId, eventType);
|
||||||
|
let _failureCallback;
|
||||||
|
let _context;
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
_context = context;
|
||||||
|
_failureCallback = failureCallbackOrContext;
|
||||||
|
} else if (isFunction(failureCallbackOrContext)) {
|
||||||
|
_failureCallback = failureCallbackOrContext;
|
||||||
|
} else {
|
||||||
|
_context = failureCallbackOrContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_failureCallback) {
|
||||||
|
_failureCallback = (error) => {
|
||||||
|
if (error.message.startsWith('FirebaseError: permission_denied')) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
error.message = `permission_denied at /${this.path}: Client doesn't have permission to access the desired data.`
|
||||||
|
}
|
||||||
|
|
||||||
|
failureCallbackOrContext(error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let _successCallback;
|
||||||
|
|
||||||
|
if (_context) {
|
||||||
|
_successCallback = successCallback.bind(_context);
|
||||||
|
} else {
|
||||||
|
_successCallback = successCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = {
|
||||||
|
listenerId: Object.keys(this.refListeners).length + 1,
|
||||||
|
eventName: eventType,
|
||||||
|
successCallback: _successCallback,
|
||||||
|
failureCallback: _failureCallback,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.refListeners[listener.listenerId] = listener;
|
||||||
|
this.database.on(this, listener);
|
||||||
|
return successCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches a callback attached with on().
|
||||||
|
*
|
||||||
|
* Calling off() on a parent listener will not automatically remove listeners
|
||||||
|
* registered on child nodes.
|
||||||
|
*
|
||||||
|
* If on() was called multiple times with the same eventType off() must be
|
||||||
|
* called multiple times to completely remove it.
|
||||||
|
*
|
||||||
|
* If a callback is not specified, all callbacks for the specified eventType
|
||||||
|
* will be removed. If no eventType or callback is specified, all callbacks
|
||||||
|
* for the Reference will be removed.
|
||||||
|
*
|
||||||
|
* If a context is specified, it too is used as a filter parameter: a callback
|
||||||
|
* will only be detached if, when it was attached with on(), the same event type,
|
||||||
|
* callback function and context were provided.
|
||||||
|
*
|
||||||
|
* If no callbacks matching the parameters provided are found, no callbacks are
|
||||||
|
* detached.
|
||||||
|
*
|
||||||
|
* @param {('value'|'child_added'|'child_changed'|'child_removed'|'child_moved')=} eventType - Type of event to detach callback for.
|
||||||
|
* @param {Function=} originalCallback - Original callback passed to on()
|
||||||
|
* TODO @param {*=} context - The context passed to on() when the callback was bound
|
||||||
|
*
|
||||||
|
* {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference#off}
|
||||||
|
*/
|
||||||
|
off(eventType?: string = '', originalCallback?: () => any) {
|
||||||
|
// TODO this.log.debug('ref.off(): ', this.refId, eventType);
|
||||||
|
// $FlowFixMe
|
||||||
|
const listeners: Array<DatabaseListener> = Object.values(this.refListeners);
|
||||||
|
let listenersToRemove;
|
||||||
|
if (eventType && originalCallback) {
|
||||||
|
listenersToRemove = listeners.filter((listener) => {
|
||||||
|
return listener.eventName === eventType && listener.successCallback === originalCallback;
|
||||||
|
});
|
||||||
|
// Only remove a single listener as per the web spec
|
||||||
|
if (listenersToRemove.length > 1) listenersToRemove = [listenersToRemove[0]];
|
||||||
|
} else if (eventType) {
|
||||||
|
listenersToRemove = listeners.filter((listener) => {
|
||||||
|
return listener.eventName === eventType;
|
||||||
|
});
|
||||||
|
} else if (originalCallback) {
|
||||||
|
listenersToRemove = listeners.filter((listener) => {
|
||||||
|
return listener.successCallback === originalCallback;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
listenersToRemove = listeners;
|
||||||
|
}
|
||||||
|
// Remove the listeners from the reference to prevent memory leaks
|
||||||
|
listenersToRemove.forEach((listener) => {
|
||||||
|
delete this.refListeners[listener.listenerId];
|
||||||
|
});
|
||||||
|
return this.database.off(this.refId, listenersToRemove, Object.keys(this.refListeners).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Database representation wrapper
|
* Database representation wrapper
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generatePushID } from './../../utils';
|
let transactionId = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Database
|
* @class Database
|
||||||
|
@ -12,9 +12,10 @@ export default class TransactionHandler {
|
||||||
constructor(database: Object) {
|
constructor(database: Object) {
|
||||||
this._transactions = {};
|
this._transactions = {};
|
||||||
this._database = database;
|
this._database = database;
|
||||||
this._transactionListener = this._database._eventEmitter.addListener(
|
|
||||||
'database_transaction_event',
|
this._transactionListener = this._database.addListener(
|
||||||
event => this._handleTransactionEvent(event),
|
this._database._getAppEventName('database_transaction_event'),
|
||||||
|
this._handleTransactionEvent.bind(this),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,12 +26,7 @@ export default class TransactionHandler {
|
||||||
* @param onComplete
|
* @param onComplete
|
||||||
* @param applyLocally
|
* @param applyLocally
|
||||||
*/
|
*/
|
||||||
add(
|
add(reference: Object, transactionUpdater: Function, onComplete?: Function, applyLocally?: boolean = false) {
|
||||||
reference: Object,
|
|
||||||
transactionUpdater: Function,
|
|
||||||
onComplete?: Function,
|
|
||||||
applyLocally?: boolean = false
|
|
||||||
) {
|
|
||||||
const id = this._generateTransactionId();
|
const id = this._generateTransactionId();
|
||||||
|
|
||||||
this._transactions[id] = {
|
this._transactions[id] = {
|
||||||
|
@ -43,7 +39,7 @@ export default class TransactionHandler {
|
||||||
started: true,
|
started: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._database._native.startTransaction(reference.path, id, applyLocally);
|
this._database._native.transactionStart(reference.path, id, applyLocally);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +52,7 @@ export default class TransactionHandler {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_generateTransactionId(): string {
|
_generateTransactionId(): string {
|
||||||
return generatePushID(this._database.serverTimeOffset);
|
return transactionId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +86,8 @@ export default class TransactionHandler {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const transaction = this._transactions[id];
|
const transaction = this._transactions[id];
|
||||||
// todo handle when transaction no longer exists on js side?
|
if (!transaction) return;
|
||||||
|
|
||||||
newValue = transaction.transactionUpdater(value);
|
newValue = transaction.transactionUpdater(value);
|
||||||
} finally {
|
} finally {
|
||||||
let abort = false;
|
let abort = false;
|
||||||
|
@ -99,7 +96,7 @@ export default class TransactionHandler {
|
||||||
abort = true;
|
abort = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._database._native.tryCommitTransaction(id, { value: newValue, abort });
|
this._database._native.transactionTryCommit(id, { value: newValue, abort });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +110,7 @@ export default class TransactionHandler {
|
||||||
if (transaction && !transaction.completed) {
|
if (transaction && !transaction.completed) {
|
||||||
transaction.completed = true;
|
transaction.completed = true;
|
||||||
try {
|
try {
|
||||||
transaction.onComplete(new Error(event.message, event.code), null);
|
transaction.onComplete(new Error(event.error.message, event.error.code), null);
|
||||||
} finally {
|
} finally {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
delete this._transactions[event.id];
|
delete this._transactions[event.id];
|
||||||
|
|
|
@ -24,6 +24,15 @@ const NATIVE_MODULE_EVENTS = {
|
||||||
Auth: [
|
Auth: [
|
||||||
'onAuthStateChanged',
|
'onAuthStateChanged',
|
||||||
],
|
],
|
||||||
|
Database: [
|
||||||
|
'database_transaction_event',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULTS = {
|
||||||
|
Database: {
|
||||||
|
persistence: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModuleBase {
|
export default class ModuleBase {
|
||||||
|
@ -35,7 +44,7 @@ export default class ModuleBase {
|
||||||
* @param withEventEmitter
|
* @param withEventEmitter
|
||||||
*/
|
*/
|
||||||
constructor(firebaseApp, options, moduleName, withEventEmitter = false) {
|
constructor(firebaseApp, options, moduleName, withEventEmitter = false) {
|
||||||
this._options = Object.assign({}, options);
|
this._options = Object.assign({}, DEFAULTS[moduleName] || {}, options);
|
||||||
this._module = moduleName;
|
this._module = moduleName;
|
||||||
this._firebaseApp = firebaseApp;
|
this._firebaseApp = firebaseApp;
|
||||||
this._appName = firebaseApp._name;
|
this._appName = firebaseApp._name;
|
||||||
|
|
|
@ -9,37 +9,6 @@ const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuv
|
||||||
const hasOwnProperty = Object.hasOwnProperty;
|
const hasOwnProperty = Object.hasOwnProperty;
|
||||||
const DEFAULT_CHUNK_SIZE = 50;
|
const DEFAULT_CHUNK_SIZE = 50;
|
||||||
|
|
||||||
// internal promise handler
|
|
||||||
const _handler = (resolve, reject, errorPrefix, err, resp) => {
|
|
||||||
// resolve / reject after events etc
|
|
||||||
setImmediate(() => {
|
|
||||||
if (err) {
|
|
||||||
// $FlowFixMe
|
|
||||||
const firebaseError: FirebaseError = new Error(err.message);
|
|
||||||
|
|
||||||
if (isObject(err)) {
|
|
||||||
Object.keys(err).forEach(key => Object.defineProperty(firebaseError, key, { value: err[key] }));
|
|
||||||
if (errorPrefix) {
|
|
||||||
firebaseError.code = toWebSDKErrorCode(err.code || '', errorPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject(firebaseError);
|
|
||||||
}
|
|
||||||
return resolve(resp);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export function nativeSDKMissing(sdkName) {
|
|
||||||
console.warn(`Firebase ${sdkName} native sdk has not been included in your ${Platform.OS === 'ios' ? 'podfile' : 'build.gradle'} - ${sdkName} methods have been disabled.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toWebSDKErrorCode(code: any, prefix: string): string {
|
|
||||||
if (!code || typeof code !== 'string') return '';
|
|
||||||
return code.toLowerCase().replace('error_', prefix).replace(/_/g, '-');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deep get a value from an object.
|
* Deep get a value from an object.
|
||||||
* @website https://github.com/Salakar/deeps
|
* @website https://github.com/Salakar/deeps
|
||||||
|
@ -149,43 +118,12 @@ export function tryJSONStringify(data: any): string | null {
|
||||||
// noinspection Eslint
|
// noinspection Eslint
|
||||||
export const windowOrGlobal = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this;
|
export const windowOrGlobal = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this;
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes an objects keys it's values
|
|
||||||
* @param object
|
|
||||||
* @returns {{}}
|
|
||||||
*/
|
|
||||||
export function reverseKeyValues(object: Object): Object {
|
|
||||||
const output = {};
|
|
||||||
for (const key in object) {
|
|
||||||
output[object[key]] = key;
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No operation func
|
* No operation func
|
||||||
*/
|
*/
|
||||||
export function noop(): void {
|
export function noop(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a native module method to support promises.
|
|
||||||
* @param fn
|
|
||||||
* @param NativeModule
|
|
||||||
* @param errorPrefix
|
|
||||||
*/
|
|
||||||
export function promisify(fn: Function | string,
|
|
||||||
NativeModule: Object,
|
|
||||||
errorPrefix?: string): (args: any) => Promise<> {
|
|
||||||
return (...args) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const _fn = typeof fn === 'function' ? fn : NativeModule[fn];
|
|
||||||
if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.'));
|
|
||||||
return _fn.apply(NativeModule, [...args, _handler.bind(_handler, resolve, reject, errorPrefix)]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delays chunks based on sizes per event loop.
|
* Delays chunks based on sizes per event loop.
|
||||||
|
@ -245,6 +183,11 @@ export function each(array: Array<*>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string typeof that's valid for Firebase usage
|
||||||
|
* @param value
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
export function typeOf(value: any): string {
|
export function typeOf(value: any): string {
|
||||||
if (value === null) return 'null';
|
if (value === null) return 'null';
|
||||||
if (Array.isArray(value)) return 'array';
|
if (Array.isArray(value)) return 'array';
|
||||||
|
|
|
@ -90,17 +90,17 @@ PODS:
|
||||||
- GoogleToolboxForMac/NSString+URLArguments (2.1.1)
|
- GoogleToolboxForMac/NSString+URLArguments (2.1.1)
|
||||||
- GTMSessionFetcher/Core (1.1.10)
|
- GTMSessionFetcher/Core (1.1.10)
|
||||||
- Protobuf (3.3.0)
|
- Protobuf (3.3.0)
|
||||||
- React (0.44.0):
|
- React (0.44.3):
|
||||||
- React/Core (= 0.44.0)
|
- React/Core (= 0.44.3)
|
||||||
- React/Core (0.44.0):
|
- React/Core (0.44.3):
|
||||||
- React/cxxreact
|
- React/cxxreact
|
||||||
- Yoga (= 0.44.0.React)
|
- Yoga (= 0.44.3.React)
|
||||||
- React/cxxreact (0.44.0):
|
- React/cxxreact (0.44.3):
|
||||||
- React/jschelpers
|
- React/jschelpers
|
||||||
- React/jschelpers (0.44.0)
|
- React/jschelpers (0.44.3)
|
||||||
- RNFirebase (2.0.4):
|
- RNFirebase (2.0.4):
|
||||||
- React
|
- React
|
||||||
- Yoga (0.44.0.React)
|
- Yoga (0.44.3.React)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Firebase/AdMob
|
- Firebase/AdMob
|
||||||
|
@ -143,9 +143,9 @@ SPEC CHECKSUMS:
|
||||||
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
|
GoogleToolboxForMac: 8e329f1b599f2512c6b10676d45736bcc2cbbeb0
|
||||||
GTMSessionFetcher: 30d874b96d0d76028f61fbd122801e3f030d47db
|
GTMSessionFetcher: 30d874b96d0d76028f61fbd122801e3f030d47db
|
||||||
Protobuf: d582fecf68201eac3d79ed61369ef45734394b9c
|
Protobuf: d582fecf68201eac3d79ed61369ef45734394b9c
|
||||||
React: d2077cc20245ccdc8bfe1fdc002f2003318ae8d8
|
React: 6361345ebeb769a929e10a06baf0c868d6d03ad5
|
||||||
RNFirebase: 3e5a3ff431c5c8a997152f5f19d354e1ca66f65d
|
RNFirebase: 3e5a3ff431c5c8a997152f5f19d354e1ca66f65d
|
||||||
Yoga: a92a5d8e128905bf9f29c82f870192a6e873dd98
|
Yoga: c90474ca3ec1edba44c97b6c381f03e222a9e287
|
||||||
|
|
||||||
PODFILE CHECKSUM: 45666f734ebfc8b3b0f2be0a83bc2680caeb502f
|
PODFILE CHECKSUM: 45666f734ebfc8b3b0f2be0a83bc2680caeb502f
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,11 @@ const EVENTS = {
|
||||||
TEST_STATUS: 'TEST_STATUS',
|
TEST_STATUS: 'TEST_STATUS',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!console.groupCollapsed) {
|
||||||
|
console.groupCollapsed = console.log;
|
||||||
|
console.groupEnd = () => console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
const locationRegex = /\(?http:.*:([0-9]+):([0-9]+)\)?/g;
|
const locationRegex = /\(?http:.*:([0-9]+):([0-9]+)\)?/g;
|
||||||
|
|
||||||
function cleanStack(stack, maxLines = 5) {
|
function cleanStack(stack, maxLines = 5) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { setupSuites } from './tests/index';
|
||||||
|
|
||||||
global.Promise = require('bluebird');
|
global.Promise = require('bluebird');
|
||||||
|
|
||||||
|
console.ignoredYellowBox = ['Setting a timer for a long period of time, i.e. multiple minutes'];
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
store: any,
|
store: any,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function childTests({ describe, it, context, firebase }) {
|
function childTests({ fdescribe, it, context, firebase }) {
|
||||||
describe('ref().child', () => {
|
fdescribe('ref().child', () => {
|
||||||
context('when passed a shallow path', () => {
|
context('when passed a shallow path', () => {
|
||||||
it('returns correct child ref', () => {
|
it('returns correct child ref', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function factoryTests({ describe, it, firebase }) {
|
function factoryTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref()', () => {
|
fdescribe('ref()', () => {
|
||||||
it('returns root reference when provided no path', () => {
|
it('returns root reference when provided no path', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function isEqualTests({ describe, before, it, firebase }) {
|
function isEqualTests({ fdescribe, before, it, firebase }) {
|
||||||
describe('ref().isEqual()', () => {
|
fdescribe('ref().isEqual()', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
this.ref = firebase.native.database().ref('tests/types');
|
this.ref = firebase.native.database().ref('tests/types');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import should from 'should';
|
import should from 'should';
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function issueTests({ describe, it, context, firebase }) {
|
function issueTests({ fdescribe, it, context, firebase }) {
|
||||||
describe('issue_100', () => {
|
fdescribe('issue_100', () => {
|
||||||
context('array-like values should', () => {
|
context('array-like values should', () => {
|
||||||
it('return null in returned array at positions where a key is missing', async () => {
|
it('return null in returned array at positions where a key is missing', async () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
@ -18,7 +18,7 @@ function issueTests({ describe, it, context, firebase }) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('issue_108', () => {
|
fdescribe('issue_108', () => {
|
||||||
context('filters using floats', () => {
|
context('filters using floats', () => {
|
||||||
it('return correct results', async () => {
|
it('return correct results', async () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
@ -67,7 +67,7 @@ function issueTests({ describe, it, context, firebase }) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('issue_171', () => {
|
fdescribe('issue_171', () => {
|
||||||
context('non array-like values should', () => {
|
context('non array-like values should', () => {
|
||||||
it('return as objects', async () => {
|
it('return as objects', async () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function keyTests({ describe, it, firebase }) {
|
function keyTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref().key', () => {
|
fdescribe('ref().key', () => {
|
||||||
it('returns null for root ref', () => {
|
it('returns null for root ref', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'should-sinon';
|
||||||
|
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function onceTests({ describe, firebase, it, tryCatch }) {
|
function onceTests({ fdescribe, firebase, it, tryCatch }) {
|
||||||
describe('ref().once()', () => {
|
fdescribe('ref().once()', () => {
|
||||||
it('returns a promise', () => {
|
it('returns a promise', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
|
@ -62,8 +62,7 @@ function onceTests({ describe, firebase, it, tryCatch }) {
|
||||||
|
|
||||||
const failureCb = tryCatch((error) => {
|
const failureCb = tryCatch((error) => {
|
||||||
// Assertion
|
// Assertion
|
||||||
|
error.code.includes('DATABASE/PERMISSION-DENIED').should.be.true();
|
||||||
error.message.includes('permission_denied').should.be.true();
|
|
||||||
resolve();
|
resolve();
|
||||||
}, reject);
|
}, reject);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function parentTests({ describe, context, it, firebase }) {
|
function parentTests({ fdescribe, context, it, firebase }) {
|
||||||
describe('ref().parent', () => {
|
fdescribe('ref().parent', () => {
|
||||||
context('on the root ref', () => {
|
context('on the root ref', () => {
|
||||||
it('returns null', () => {
|
it('returns null', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function setTests({ describe, it, firebase }) {
|
function setTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref().priority', () => {
|
fdescribe('ref().priority', () => {
|
||||||
it('setPriority() should correctly set a priority for all non-null values', async () => {
|
it('setPriority() should correctly set a priority for all non-null values', async () => {
|
||||||
await Promise.map(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
await Promise.map(Object.keys(DatabaseContents.DEFAULT), async (dataRef) => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'should-sinon';
|
||||||
|
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function pushTests({ describe, it, firebase }) {
|
function pushTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref().push()', () => {
|
fdescribe('ref().push()', () => {
|
||||||
it('returns a ref that can be used to set value later', async () => {
|
it('returns a ref that can be used to set value later', async () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'should-sinon';
|
import 'should-sinon';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
function queryTests({ describe, it, firebase, tryCatch }) {
|
function queryTests({ fdescribe, it, firebase, tryCatch }) {
|
||||||
describe('ref query', () => {
|
fdescribe('ref query', () => {
|
||||||
it('orderByChild().equalTo()', () => {
|
it('orderByChild().equalTo()', () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const successCb = tryCatch((snapshot) => {
|
const successCb = tryCatch((snapshot) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function refTests({ describe, it, firebase }) {
|
function refTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref().ref', () => {
|
fdescribe('ref().ref', () => {
|
||||||
it('returns the reference', () => {
|
it('returns the reference', () => {
|
||||||
// Setup
|
// Setup
|
||||||
const ref = firebase.native.database().ref();
|
const ref = firebase.native.database().ref();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function removeTests({ describe, it, firebase }) {
|
function removeTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref().remove()', () => {
|
fdescribe('ref().remove()', () => {
|
||||||
it('returns a promise', () => {
|
it('returns a promise', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function rootTests({ describe, it, context, firebase }) {
|
function rootTests({ fdescribe, it, context, firebase }) {
|
||||||
describe('ref().root', () => {
|
fdescribe('ref().root', () => {
|
||||||
context('when called on a non-root reference', () => {
|
context('when called on a non-root reference', () => {
|
||||||
it('returns root ref', () => {
|
it('returns root ref', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function setTests({ describe, it, xit, firebase }) {
|
function setTests({ fdescribe, it, xit, firebase }) {
|
||||||
describe('ref.set()', () => {
|
fdescribe('ref.set()', () => {
|
||||||
xit('returns a promise', async () => {
|
it('returns a promise', async () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
const ref = firebase.native.database().ref('tests/types/number');
|
const ref = firebase.native.database().ref('tests/types/number');
|
||||||
|
@ -16,7 +16,7 @@ function setTests({ describe, it, xit, firebase }) {
|
||||||
returnValue.should.be.Promise();
|
returnValue.should.be.Promise();
|
||||||
|
|
||||||
await returnValue.then((value) => {
|
await returnValue.then((value) => {
|
||||||
(value === undefined).should.be.true();
|
(value === null).should.be.true();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
|
|
||||||
function onTests({ describe, it, firebase, tryCatch }) {
|
function onTests({ fdescribe, it, firebase, tryCatch }) {
|
||||||
describe('ref.transaction()', () => {
|
fdescribe('ref.transaction()', () => {
|
||||||
it('works', () => {
|
it('increments a value on a ref', () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let valueBefore = 1;
|
let valueBefore = 1;
|
||||||
|
|
||||||
firebase.native.database()
|
firebase.native.database()
|
||||||
.ref('tests/transaction').transaction((currentData) => {
|
.ref('tests/transaction').transaction((currentData) => {
|
||||||
if (currentData === null) {
|
if (currentData === null) {
|
||||||
return valueBefore + 10;
|
|
||||||
}
|
|
||||||
valueBefore = currentData;
|
|
||||||
return valueBefore + 10;
|
return valueBefore + 10;
|
||||||
}, tryCatch((error, committed, snapshot) => {
|
}
|
||||||
if (error) {
|
valueBefore = currentData;
|
||||||
return reject(error);
|
return valueBefore + 10;
|
||||||
}
|
}, tryCatch((error, committed, snapshot) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (!committed) {
|
if (!committed) {
|
||||||
return reject(new Error('Transaction did not commit.'));
|
return reject(new Error('Transaction did not commit.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot.val().should.equal(valueBefore + 10);
|
snapshot.val().should.equal(valueBefore + 10);
|
||||||
|
|
||||||
return resolve();
|
return resolve();
|
||||||
}, reject), true);
|
}, reject), true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,18 +33,18 @@ function onTests({ describe, it, firebase, tryCatch }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
firebase.native.database()
|
firebase.native.database()
|
||||||
.ref('tests/transaction').transaction(() => {
|
.ref('tests/transaction').transaction(() => {
|
||||||
return undefined;
|
return undefined;
|
||||||
}, (error, committed) => {
|
}, (error, committed) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!committed) {
|
if (!committed) {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return reject(new Error('Transaction did not abort commit.'));
|
return reject(new Error('Transaction did not abort commit.'));
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import DatabaseContents from '../../support/DatabaseContents';
|
import DatabaseContents from '../../support/DatabaseContents';
|
||||||
|
|
||||||
function updateTests({ describe, it, firebase }) {
|
function updateTests({ fdescribe, it, firebase }) {
|
||||||
describe('ref().update()', () => {
|
fdescribe('ref().update()', () => {
|
||||||
it('returns a promise', () => {
|
it('returns a promise', () => {
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,12 @@ function storageTests({ describe, it, firebase, tryCatch }) {
|
||||||
resolve();
|
resolve();
|
||||||
}, reject);
|
}, reject);
|
||||||
|
|
||||||
firebase.native.storage().ref('/not.jpg').downloadFile(`${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/not.jpg`).then(successCb).catch(failureCb);
|
firebase.native.storage().ref('/not.jpg')
|
||||||
|
.downloadFile(
|
||||||
|
`${firebase.native.storage.Native.DOCUMENT_DIRECTORY_PATH}/not.jpg`,
|
||||||
|
)
|
||||||
|
.then(successCb)
|
||||||
|
.catch(failureCb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue