[database][wip] on/off logic refactor - heavily still wip so things still to change
This commit is contained in:
parent
0675aa076d
commit
f1709970e9
|
@ -81,10 +81,9 @@ public class Utils {
|
|||
* @param path
|
||||
* @param dataSnapshot
|
||||
* @param refId
|
||||
* @param listenerId
|
||||
* @return
|
||||
*/
|
||||
public static WritableMap snapshotToMap(String name, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName, int refId, int listenerId) {
|
||||
public static WritableMap snapshotToMap(String name, String path, DataSnapshot dataSnapshot, @Nullable String previousChildName, int refId) {
|
||||
WritableMap snapshot = Arguments.createMap();
|
||||
WritableMap eventMap = Arguments.createMap();
|
||||
|
||||
|
@ -111,7 +110,6 @@ public class Utils {
|
|||
eventMap.putString("path", path);
|
||||
eventMap.putMap("snapshot", snapshot);
|
||||
eventMap.putString("eventName", name);
|
||||
eventMap.putInt("listenerId", listenerId);
|
||||
eventMap.putString("previousChildName", previousChildName);
|
||||
|
||||
return eventMap;
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
|||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
|
||||
import com.google.firebase.FirebaseApp;
|
||||
import com.google.firebase.database.ChildEventListener;
|
||||
import com.google.firebase.database.MutableData;
|
||||
import com.google.firebase.database.OnDisconnect;
|
||||
import com.google.firebase.database.ServerValue;
|
||||
|
@ -21,6 +22,7 @@ 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 com.google.firebase.database.ValueEventListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -31,11 +33,39 @@ import io.invertase.firebase.Utils;
|
|||
|
||||
public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFirebaseDatabase";
|
||||
private HashMap<String, ChildEventListener> childEventListeners;
|
||||
private HashMap<String, ValueEventListener> valueEventListeners;
|
||||
private SparseArray<RNFirebaseDatabaseReference> references = new SparseArray<>();
|
||||
private SparseArray<RNFirebaseTransactionHandler> transactionHandlers = new SparseArray<>();
|
||||
|
||||
RNFirebaseDatabase(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
childEventListeners = new HashMap<String, ChildEventListener>();
|
||||
valueEventListeners = new HashMap<String, ValueEventListener>();
|
||||
}
|
||||
|
||||
Boolean hasValueEventListener(String queryKey) {
|
||||
return valueEventListeners.containsKey(queryKey);
|
||||
}
|
||||
|
||||
Boolean hasChildEventListener(String queryKey) {
|
||||
return childEventListeners.containsKey(queryKey);
|
||||
}
|
||||
|
||||
void addValueEventListener(String queryKey, ValueEventListener listener) {
|
||||
valueEventListeners.put(queryKey, listener);
|
||||
}
|
||||
|
||||
void addChildEventListener(String queryKey, ChildEventListener listener) {
|
||||
childEventListeners.put(queryKey, listener);
|
||||
}
|
||||
|
||||
void removeValueEventListener(String queryKey) {
|
||||
valueEventListeners.remove(queryKey);
|
||||
}
|
||||
|
||||
void removeChildEventListener(String queryKey) {
|
||||
childEventListeners.remove(queryKey);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -394,17 +424,20 @@ public class RNFirebaseDatabase extends ReactContextBaseJavaModule {
|
|||
/**
|
||||
* Subscribe to real time events for the specified database path + modifiers
|
||||
*
|
||||
* @param appName
|
||||
* @param refId
|
||||
* @param path
|
||||
* @param modifiers
|
||||
* @param eventName
|
||||
* @param promise
|
||||
* @param appName String
|
||||
* @param args ReadableMap
|
||||
*/
|
||||
@ReactMethod
|
||||
public void on(String appName, int refId, String path, ReadableArray modifiers, String eventName, Promise promise) {
|
||||
getInternalReferenceForApp(appName, refId, path, modifiers, true).on(eventName);
|
||||
public void on(String appName, ReadableMap args) {
|
||||
RNFirebaseDatabaseReference ref = getInternalReferenceForApp(
|
||||
appName,
|
||||
args.getInt("id"),
|
||||
args.getString("path"),
|
||||
args.getArray("modifiers"),
|
||||
true
|
||||
);
|
||||
|
||||
ref.on(this, args.getString("eventType"), args.getString("eventQueryKey"));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.invertase.firebase.database;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -9,6 +10,7 @@ import android.util.SparseArray;
|
|||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
|
@ -30,8 +32,7 @@ public class RNFirebaseDatabaseReference {
|
|||
private String path;
|
||||
private String appName;
|
||||
private ReactContext reactContext;
|
||||
private SparseArray<ChildEventListener> childEventListeners;
|
||||
private SparseArray<ValueEventListener> valueEventListeners;
|
||||
|
||||
|
||||
Query getQuery() {
|
||||
return query;
|
||||
|
@ -49,8 +50,6 @@ public class RNFirebaseDatabaseReference {
|
|||
appName = app;
|
||||
path = refPath;
|
||||
reactContext = context;
|
||||
childEventListeners = new SparseArray<ChildEventListener>();
|
||||
valueEventListeners = new SparseArray<ValueEventListener>();
|
||||
query = buildDatabaseQueryAtPathAndModifiers(path, modifiersArray);
|
||||
}
|
||||
|
||||
|
@ -63,7 +62,7 @@ public class RNFirebaseDatabaseReference {
|
|||
ValueEventListener onceValueEventListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||
WritableMap data = Utils.snapshotToMap("value", path, dataSnapshot, null, refId, 0);
|
||||
WritableMap data = Utils.snapshotToMap("value", path, dataSnapshot, null, refId);
|
||||
promise.resolve(data);
|
||||
}
|
||||
|
||||
|
@ -90,7 +89,7 @@ public class RNFirebaseDatabaseReference {
|
|||
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);
|
||||
WritableMap data = Utils.snapshotToMap("child_added", path, dataSnapshot, previousChildName, refId);
|
||||
promise.resolve(data);
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +98,7 @@ public class RNFirebaseDatabaseReference {
|
|||
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);
|
||||
WritableMap data = Utils.snapshotToMap("child_changed", path, dataSnapshot, previousChildName, refId);
|
||||
promise.resolve(data);
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +107,7 @@ public class RNFirebaseDatabaseReference {
|
|||
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
||||
if ("child_removed".equals(eventName)) {
|
||||
query.removeEventListener(this);
|
||||
WritableMap data = Utils.snapshotToMap("child_removed", path, dataSnapshot, null, refId, 0);
|
||||
WritableMap data = Utils.snapshotToMap("child_removed", path, dataSnapshot, null, refId);
|
||||
promise.resolve(data);
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +116,7 @@ public class RNFirebaseDatabaseReference {
|
|||
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);
|
||||
WritableMap data = Utils.snapshotToMap("child_moved", path, dataSnapshot, previousChildName, refId);
|
||||
promise.resolve(data);
|
||||
}
|
||||
}
|
||||
|
@ -135,14 +134,20 @@ public class RNFirebaseDatabaseReference {
|
|||
|
||||
/**
|
||||
* Handles a React Native JS 'on' request and initializes listeners.
|
||||
*
|
||||
* @param eventName
|
||||
*/
|
||||
void on(String eventName) {
|
||||
|
||||
void on(RNFirebaseDatabase database, String eventName, String queryKey) {
|
||||
if (eventName.equals("value")) {
|
||||
addValueEventListener(queryKey, database);
|
||||
} else {
|
||||
addChildEventListener(queryKey, database, eventName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a React Native JS 'once' request.
|
||||
*
|
||||
* @param eventName
|
||||
* @param promise
|
||||
*/
|
||||
|
@ -155,136 +160,153 @@ public class RNFirebaseDatabaseReference {
|
|||
}
|
||||
|
||||
|
||||
// todo cleanup all below
|
||||
|
||||
void addChildEventListener(final int listenerId, final String eventName) {
|
||||
if (childEventListeners.get(listenerId) != null) {
|
||||
/**
|
||||
* @param queryKey
|
||||
* @param eventName
|
||||
*/
|
||||
private void addChildEventListener(final String queryKey, final RNFirebaseDatabase database, final String eventName) {
|
||||
if (!database.hasChildEventListener(queryKey)) {
|
||||
ChildEventListener childEventListener = new ChildEventListener() {
|
||||
@Override
|
||||
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
if ("child_added".equals(eventName)) {
|
||||
handleDatabaseEvent("child_added", listenerId, dataSnapshot, previousChildName);
|
||||
handleDatabaseEvent("child_added", queryKey, dataSnapshot, previousChildName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
if ("child_changed".equals(eventName)) {
|
||||
handleDatabaseEvent("child_changed", listenerId, dataSnapshot, previousChildName);
|
||||
handleDatabaseEvent("child_changed", queryKey, dataSnapshot, previousChildName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildRemoved(DataSnapshot dataSnapshot) {
|
||||
if ("child_removed".equals(eventName)) {
|
||||
handleDatabaseEvent("child_removed", listenerId, dataSnapshot, null);
|
||||
handleDatabaseEvent("child_removed", queryKey, dataSnapshot, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
|
||||
if ("child_moved".equals(eventName)) {
|
||||
handleDatabaseEvent("child_moved", listenerId, dataSnapshot, previousChildName);
|
||||
handleDatabaseEvent("child_moved", queryKey, dataSnapshot, previousChildName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
removeChildEventListener(listenerId);
|
||||
handleDatabaseError(listenerId, error);
|
||||
query.removeEventListener(this);
|
||||
database.removeChildEventListener(queryKey);
|
||||
handleDatabaseError(queryKey, error);
|
||||
}
|
||||
};
|
||||
|
||||
childEventListeners.put(listenerId, childEventListener);
|
||||
database.addChildEventListener(queryKey, childEventListener);
|
||||
query.addChildEventListener(childEventListener);
|
||||
Log.d(TAG, "Added ChildEventListener for refId: " + refId + " listenerId: " + listenerId);
|
||||
} else {
|
||||
Log.d(TAG, "ChildEventListener for refId: " + refId + " listenerId: " + listenerId + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
void addValueEventListener(final int listenerId) {
|
||||
if (valueEventListeners.get(listenerId) != null) {
|
||||
/**
|
||||
* @param queryKey
|
||||
*/
|
||||
private void addValueEventListener(final String queryKey, final RNFirebaseDatabase database) {
|
||||
if (!database.hasValueEventListener(queryKey)) {
|
||||
ValueEventListener valueEventListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(DataSnapshot dataSnapshot) {
|
||||
handleDatabaseEvent("value", listenerId, dataSnapshot, null);
|
||||
handleDatabaseEvent("value", queryKey, dataSnapshot, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(DatabaseError error) {
|
||||
removeValueEventListener(listenerId);
|
||||
handleDatabaseError(listenerId, error);
|
||||
query.removeEventListener(this);
|
||||
database.removeValueEventListener(queryKey);
|
||||
handleDatabaseError(queryKey, error);
|
||||
}
|
||||
};
|
||||
|
||||
valueEventListeners.put(listenerId, valueEventListener);
|
||||
database.addValueEventListener(queryKey, valueEventListener);
|
||||
query.addValueEventListener(valueEventListener);
|
||||
Log.d(TAG, "Added ValueEventListener for refId: " + refId + " listenerId: " + listenerId);
|
||||
} else {
|
||||
Log.d(TAG, "ValueEventListener for refId: " + refId + " listenerId: " + listenerId + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void removeEventListener(int listenerId, String eventName) {
|
||||
if ("value".equals(eventName)) {
|
||||
this.removeValueEventListener(listenerId);
|
||||
} else {
|
||||
this.removeChildEventListener(listenerId);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasListeners() {
|
||||
return childEventListeners.size() > 0 || valueEventListeners.size() > 0;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
Log.d(TAG, "cleaning up database reference " + this);
|
||||
this.removeChildEventListener(null);
|
||||
this.removeValueEventListener(null);
|
||||
}
|
||||
|
||||
private void removeChildEventListener(Integer listenerId) {
|
||||
ChildEventListener listener = childEventListeners.get(listenerId);
|
||||
if (listener != null) {
|
||||
query.removeEventListener(listener);
|
||||
childEventListeners.delete(listenerId);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeValueEventListener(Integer listenerId) {
|
||||
ValueEventListener listener = valueEventListeners.get(listenerId);
|
||||
if (listener != null) {
|
||||
query.removeEventListener(listener);
|
||||
valueEventListeners.delete(listenerId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDatabaseEvent(final String name, final Integer listenerId, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
||||
WritableMap data = Utils.snapshotToMap(name, path, dataSnapshot, previousChildName, refId, listenerId);
|
||||
/**
|
||||
* @param name
|
||||
* @param dataSnapshot
|
||||
* @param previousChildName
|
||||
*/
|
||||
private void handleDatabaseEvent(final String name, String queryKey, final DataSnapshot dataSnapshot, @Nullable String previousChildName) {
|
||||
WritableMap evt = Arguments.createMap();
|
||||
evt.putString("eventName", name);
|
||||
evt.putMap("body", data);
|
||||
WritableMap data = Utils.snapshotToMap(name, path, dataSnapshot, previousChildName, refId);
|
||||
|
||||
Utils.sendEvent(reactContext, "database_event", evt);
|
||||
evt.putMap("body", data);
|
||||
evt.putInt("refId", refId);
|
||||
evt.putString("eventName", name);
|
||||
evt.putString("appName", appName);
|
||||
evt.putString("queryKey", queryKey);
|
||||
|
||||
Utils.sendEvent(reactContext, "database_on_event", evt);
|
||||
}
|
||||
|
||||
private void handleDatabaseError(final Integer listenerId, final DatabaseError error) {
|
||||
/**
|
||||
* Handles a database listener cancellation error.
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
private void handleDatabaseError(String queryKey, final DatabaseError error) {
|
||||
WritableMap errMap = Arguments.createMap();
|
||||
|
||||
errMap.putInt("refId", refId);
|
||||
if (listenerId != null) {
|
||||
errMap.putInt("listenerId", listenerId);
|
||||
}
|
||||
errMap.putString("path", path);
|
||||
errMap.putString("appName", appName);
|
||||
errMap.putString("queryKey", queryKey);
|
||||
errMap.putInt("code", error.getCode());
|
||||
errMap.putString("details", error.getDetails());
|
||||
errMap.putString("message", error.getMessage());
|
||||
|
||||
Utils.sendEvent(reactContext, "database_error", errMap);
|
||||
Utils.sendEvent(reactContext, "database_cancel_event", errMap);
|
||||
}
|
||||
|
||||
|
||||
// todo cleanup below
|
||||
|
||||
// void removeEventListener(int listenerId, String eventName) {
|
||||
// if ("value".equals(eventName)) {
|
||||
// removeValueEventListener(listenerId);
|
||||
// } else {
|
||||
// removeChildEventListener(listenerId);
|
||||
// }
|
||||
// }
|
||||
|
||||
// boolean hasListeners() {
|
||||
// return childEventListeners.size() > 0 || valueEventListeners.size() > 0;
|
||||
// }
|
||||
//
|
||||
// public void cleanup() {
|
||||
// Log.d(TAG, "cleaning up database reference " + this);
|
||||
// this.removeChildEventListener(null);
|
||||
// this.removeValueEventListener(null);
|
||||
// }
|
||||
|
||||
// private void removeChildEventListener(Integer listenerId) {
|
||||
// ChildEventListener listener = childEventListeners.get(listenerId);
|
||||
// if (listener != null) {
|
||||
// query.removeEventListener(listener);
|
||||
// childEventListeners.remove(listenerId);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void removeValueEventListener(Integer listenerId) {
|
||||
// ValueEventListener listener = valueEventListeners.get(listenerId);
|
||||
// if (listener != null) {
|
||||
// query.removeEventListener(listener);
|
||||
// valueEventListeners.delete(listenerId);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
private Query buildDatabaseQueryAtPathAndModifiers(String path, ReadableArray modifiers) {
|
||||
FirebaseDatabase firebaseDatabase = RNFirebaseDatabase.getDatabaseForApp(appName);
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ static NSString *const AUTH_ERROR_EVENT = @"authError";
|
|||
static NSString *const AUTH_ANONYMOUS_ERROR_EVENT = @"authAnonymousError";
|
||||
|
||||
// Database
|
||||
static NSString *const DATABASE_DATA_EVENT = @"database_event";
|
||||
static NSString *const DATABASE_ERROR_EVENT = @"database_error";
|
||||
static NSString *const DATABASE_ON_EVENT = @"database_on_event";
|
||||
static NSString *const DATABASE_CANCEL_EVENT = @"database_cancel_event";
|
||||
static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_event";
|
||||
|
||||
static NSString *const DATABASE_VALUE_EVENT = @"value";
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#import "RNFirebaseDatabase.h"
|
||||
|
||||
#if __has_include(<FirebaseDatabase/FIRDatabase.h>)
|
||||
|
||||
#import "RNFirebaseDatabaseReference.h"
|
||||
#import "RNFirebaseEvents.h"
|
||||
|
||||
@implementation RNFirebaseDatabase
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
// TODO document methods
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
|
@ -17,32 +20,42 @@ RCT_EXPORT_MODULE();
|
|||
return self;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(goOnline:(NSString *) appName) {
|
||||
RCT_EXPORT_METHOD(goOnline:
|
||||
(NSString *) appName) {
|
||||
[[RNFirebaseDatabase getDatabaseForApp:appName] goOnline];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(goOffline:(NSString *) appName) {
|
||||
RCT_EXPORT_METHOD(goOffline:
|
||||
(NSString *) appName) {
|
||||
[[RNFirebaseDatabase getDatabaseForApp:appName] goOffline];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setPersistence:(NSString *) appName
|
||||
state:(BOOL) state) {
|
||||
RCT_EXPORT_METHOD(setPersistence:
|
||||
(NSString *) appName
|
||||
state:
|
||||
(BOOL) state) {
|
||||
[RNFirebaseDatabase getDatabaseForApp:appName].persistenceEnabled = state;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(keepSynced:(NSString *) appName
|
||||
refId:(nonnull NSNumber *) refId
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
state:(BOOL) state) {
|
||||
RCT_EXPORT_METHOD(keepSynced:
|
||||
(NSString *) appName
|
||||
refId:
|
||||
(nonnull
|
||||
NSNumber *) refId
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
state:(BOOL) state) {
|
||||
|
||||
FIRDatabaseQuery * query = [self getInternalReferenceForApp:appName refId:refId path:path modifiers:modifiers keep:false].query;
|
||||
FIRDatabaseQuery *query = [self getInternalReferenceForApp:appName refId:refId path:path modifiers:modifiers keep:false].query;
|
||||
[query keepSynced:state];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(transactionTryCommit:(NSString *) appName
|
||||
transactionId:(nonnull NSNumber *) transactionId
|
||||
updates:(NSDictionary *) updates) {
|
||||
RCT_EXPORT_METHOD(transactionTryCommit:
|
||||
(NSString *) appName
|
||||
transactionId:
|
||||
(nonnull
|
||||
NSNumber *) transactionId
|
||||
updates:(NSDictionary *) updates) {
|
||||
__block NSMutableDictionary *transactionState;
|
||||
|
||||
dispatch_sync(_transactionQueue, ^{
|
||||
|
@ -69,10 +82,14 @@ RCT_EXPORT_METHOD(transactionTryCommit:(NSString *) appName
|
|||
}
|
||||
|
||||
|
||||
RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
transactionId:(nonnull NSNumber *) transactionId
|
||||
applyLocally:(BOOL) applyLocally) {
|
||||
RCT_EXPORT_METHOD(transactionStart:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
transactionId:
|
||||
(nonnull
|
||||
NSNumber *) transactionId
|
||||
applyLocally:(BOOL) applyLocally) {
|
||||
dispatch_async(_transactionQueue, ^{
|
||||
NSMutableDictionary *transactionState = [NSMutableDictionary new];
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
@ -80,148 +97,194 @@ RCT_EXPORT_METHOD(transactionStart:(NSString *) appName
|
|||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
|
||||
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData *
|
||||
_Nonnull currentData) {
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions setValue:transactionState forKey:transactionId];
|
||||
NSDictionary *updateMap = [self createTransactionUpdateMap:appName transactionId:transactionId updatesData:currentData];
|
||||
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:updateMap];
|
||||
});
|
||||
_Nonnull currentData) {
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions setValue:transactionState forKey:transactionId];
|
||||
NSDictionary *updateMap = [self createTransactionUpdateMap:appName transactionId:transactionId updatesData:currentData];
|
||||
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:updateMap];
|
||||
});
|
||||
|
||||
// wait for the js event handler to call tryCommitTransaction
|
||||
// this wait occurs on the Firebase Worker Queue
|
||||
// so if the tryCommitTransaction fails to signal the semaphore
|
||||
// no further blocks will be executed by Firebase until the timeout expires
|
||||
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
|
||||
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
|
||||
// wait for the js event handler to call tryCommitTransaction
|
||||
// this wait occurs on the Firebase Worker Queue
|
||||
// so if the tryCommitTransaction fails to signal the semaphore
|
||||
// no further blocks will be executed by Firebase until the timeout expires
|
||||
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
|
||||
BOOL timedout = dispatch_semaphore_wait(sema, delayTime) != 0;
|
||||
|
||||
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
|
||||
id value = [transactionState valueForKey:@"value"];
|
||||
BOOL abort = [transactionState valueForKey:@"abort"] || timedout;
|
||||
id value = [transactionState valueForKey:@"value"];
|
||||
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions removeObjectForKey:transactionId];
|
||||
});
|
||||
dispatch_barrier_async(_transactionQueue, ^{
|
||||
[_transactions removeObjectForKey:transactionId];
|
||||
});
|
||||
|
||||
if (abort) {
|
||||
return [FIRTransactionResult abort];
|
||||
} else {
|
||||
currentData.value = value;
|
||||
return [FIRTransactionResult successWithValue:currentData];
|
||||
}
|
||||
if (abort) {
|
||||
return [FIRTransactionResult abort];
|
||||
} else {
|
||||
currentData.value = value;
|
||||
return [FIRTransactionResult successWithValue:currentData];
|
||||
}
|
||||
andCompletionBlock:
|
||||
^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
|
||||
NSDictionary *resultMap = [self createTransactionResultMap:appName transactionId:transactionId error:databaseError committed:committed snapshot:snapshot];
|
||||
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:resultMap];
|
||||
}
|
||||
withLocalEvents:
|
||||
applyLocally];
|
||||
}
|
||||
andCompletionBlock:
|
||||
^(NSError *_Nullable databaseError, BOOL committed, FIRDataSnapshot *_Nullable snapshot) {
|
||||
NSDictionary *resultMap = [self createTransactionResultMap:appName transactionId:transactionId error:databaseError committed:committed snapshot:snapshot];
|
||||
[self sendEventWithName:DATABASE_TRANSACTION_EVENT body:resultMap];
|
||||
}
|
||||
withLocalEvents:
|
||||
applyLocally];
|
||||
});
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectSet:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectSet:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
props:
|
||||
(NSDictionary *) props
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref onDisconnectSetValue:props[@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectUpdate:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectUpdate:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
props:
|
||||
(NSDictionary *) props
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref onDisconnectUpdateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectRemove:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref onDisconnectRemoveValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(onDisconnectCancel:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref cancelDisconnectOperationsWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(set:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(set:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
props:
|
||||
(NSDictionary *) props
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref setValue:[props valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setPriority:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
priority:(NSDictionary *) priority
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(setPriority:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
priority:
|
||||
(NSDictionary *) priority
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref setPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
||||
[ref setPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setWithPriority:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
data:(NSDictionary *) data
|
||||
priority:(NSDictionary *) priority
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(setWithPriority:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
data:
|
||||
(NSDictionary *) data
|
||||
priority:
|
||||
(NSDictionary *) priority
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref setValue:[data valueForKey:@"value"] andPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
||||
[ref setValue:[data valueForKey:@"value"] andPriority:[priority valueForKey:@"value"] withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(update:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
props:(NSDictionary *) props
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(update:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
props:
|
||||
(NSDictionary *) props
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref updateChildValues:props withCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(remove:(NSString *) appName
|
||||
path:(NSString *) path
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(remove:
|
||||
(NSString *) appName
|
||||
path:
|
||||
(NSString *) path
|
||||
resolver:
|
||||
(RCTPromiseResolveBlock) resolve
|
||||
rejecter:
|
||||
(RCTPromiseRejectBlock) reject) {
|
||||
FIRDatabaseReference *ref = [self getReferenceForAppPath:appName path:path];
|
||||
[ref removeValueWithCompletionBlock:^(NSError *_Nullable error, FIRDatabaseReference *_Nonnull _ref) {
|
||||
[RNFirebaseDatabase handlePromise:resolve rejecter:reject databaseError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(once:(NSString *) appName
|
||||
refId:(nonnull NSNumber *) refId
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
eventName:(NSString *) eventName
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RCT_EXPORT_METHOD(once:
|
||||
(NSString *) appName
|
||||
refId:
|
||||
(nonnull
|
||||
NSNumber *) refId
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
eventName:(NSString *) eventName
|
||||
resolver:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject) {
|
||||
RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appName refId:refId path:path modifiers:modifiers keep:false];
|
||||
[ref addSingleEventHandler:eventName resolver:resolve rejecter:reject];
|
||||
}
|
||||
|
@ -229,9 +292,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
/*
|
||||
* INTERNALS/UTILS
|
||||
*/
|
||||
+ (void) handlePromise:(RCTPromiseResolveBlock) resolve
|
||||
rejecter:(RCTPromiseRejectBlock) reject
|
||||
databaseError:(NSError *) databaseError {
|
||||
+ (void)handlePromise:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject databaseError:(NSError *)databaseError {
|
||||
if (databaseError != nil) {
|
||||
NSDictionary *jsError = [RNFirebaseDatabase getJSError:databaseError];
|
||||
reject([jsError valueForKey:@"code"], [jsError valueForKey:@"message"], databaseError);
|
||||
|
@ -240,21 +301,16 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
}
|
||||
}
|
||||
|
||||
+ (FIRDatabase *) getDatabaseForApp:(NSString *) appName {
|
||||
+ (FIRDatabase *)getDatabaseForApp:(NSString *)appName {
|
||||
FIRApp *app = [FIRApp appNamed:appName];
|
||||
return [FIRDatabase databaseForApp:app];
|
||||
}
|
||||
|
||||
- (FIRDatabaseReference *) getReferenceForAppPath:(NSString *) appName
|
||||
path:(NSString *) path {
|
||||
- (FIRDatabaseReference *)getReferenceForAppPath:(NSString *)appName path:(NSString *)path {
|
||||
return [[RNFirebaseDatabase getDatabaseForApp:appName] referenceWithPath:path];
|
||||
}
|
||||
|
||||
- (RNFirebaseDatabaseReference *) getInternalReferenceForApp:(NSString *) appName
|
||||
refId:(NSNumber *) refId
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
keep:(BOOL) keep {
|
||||
- (RNFirebaseDatabaseReference *)getInternalReferenceForApp:(NSString *)appName refId:(NSNumber *)refId path:(NSString *)path modifiers:(NSArray *)modifiers keep:(BOOL)keep {
|
||||
RNFirebaseDatabaseReference *ref = _dbReferences[refId];
|
||||
|
||||
if (ref == nil) {
|
||||
|
@ -268,18 +324,15 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
}
|
||||
|
||||
// TODO: Move to error util for use in other modules
|
||||
+ (NSString *) getMessageWithService:(NSString *) message
|
||||
service:(NSString *) service
|
||||
fullCode:(NSString *) fullCode {
|
||||
+ (NSString *)getMessageWithService:(NSString *)message service:(NSString *)service fullCode:(NSString *)fullCode {
|
||||
return [NSString stringWithFormat:@"%@: %@ (%@).", service, message, [fullCode lowercaseString]];
|
||||
}
|
||||
|
||||
+ (NSString *) getCodeWithService:(NSString *) service
|
||||
code:(NSString *) code {
|
||||
+ (NSString *)getCodeWithService:(NSString *)service code:(NSString *)code {
|
||||
return [NSString stringWithFormat:@"%@/%@", [service uppercaseString], [code uppercaseString]];
|
||||
}
|
||||
|
||||
+ (NSDictionary *) getJSError:(NSError *) nativeError {
|
||||
+ (NSDictionary *)getJSError:(NSError *)nativeError {
|
||||
NSMutableDictionary *errorMap = [[NSMutableDictionary alloc] init];
|
||||
[errorMap setValue:@(nativeError.code) forKey:@"nativeErrorCode"];
|
||||
[errorMap setValue:[nativeError localizedDescription] forKey:@"nativeErrorMessage"];
|
||||
|
@ -303,7 +356,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
message = [RNFirebaseDatabase getMessageWithService:@"The write was canceled by the user." service:service fullCode:code];
|
||||
break;
|
||||
|
||||
// TODO: Missing iOS equivalent codes
|
||||
// TODO: Missing iOS equivalent codes
|
||||
case -1:
|
||||
code = [RNFirebaseDatabase getCodeWithService:service code:@"data-stale"];
|
||||
message = [RNFirebaseDatabase getMessageWithService:@"The transaction needs to be run again with current data." service:service fullCode:code];
|
||||
|
@ -352,9 +405,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
return errorMap;
|
||||
}
|
||||
|
||||
- (NSDictionary *) createTransactionUpdateMap:(NSString *) appName
|
||||
transactionId:(NSNumber *) transactionId
|
||||
updatesData:(FIRMutableData *) updatesData {
|
||||
- (NSDictionary *)createTransactionUpdateMap:(NSString *)appName transactionId:(NSNumber *)transactionId updatesData:(FIRMutableData *)updatesData {
|
||||
NSMutableDictionary *updatesMap = [[NSMutableDictionary alloc] init];
|
||||
[updatesMap setValue:transactionId forKey:@"id"];
|
||||
[updatesMap setValue:@"update" forKey:@"type"];
|
||||
|
@ -364,11 +415,7 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
return updatesMap;
|
||||
}
|
||||
|
||||
- (NSDictionary *) createTransactionResultMap:(NSString *) appName
|
||||
transactionId:(NSNumber *) transactionId
|
||||
error:(NSError *) error
|
||||
committed:(BOOL) committed
|
||||
snapshot:(FIRDataSnapshot *) snapshot {
|
||||
- (NSDictionary *)createTransactionResultMap:(NSString *)appName transactionId:(NSNumber *)transactionId error:(NSError *)error committed:(BOOL)committed snapshot:(FIRDataSnapshot *)snapshot {
|
||||
NSMutableDictionary *resultMap = [[NSMutableDictionary alloc] init];
|
||||
[resultMap setValue:transactionId forKey:@"id"];
|
||||
[resultMap setValue:appName forKey:@"appName"];
|
||||
|
@ -388,20 +435,15 @@ RCT_EXPORT_METHOD(once:(NSString *) appName
|
|||
}
|
||||
|
||||
|
||||
/* TODO
|
||||
RCT_EXPORT_METHOD(on:
|
||||
(nonnull
|
||||
NSNumber *) refId
|
||||
path:(NSString *) path
|
||||
modifiers:(NSArray *) modifiers
|
||||
listenerId:(nonnull NSNumber *) listenerId
|
||||
name:(NSString *) eventName
|
||||
callback:(RCTResponseSenderBlock) callback) {
|
||||
RNFirebaseDatabaseReference *ref = [self getDBHandle:refId path:path modifiers:modifiers];
|
||||
[ref addEventHandler:listenerId eventName:eventName];
|
||||
callback(@[[NSNull null], @{@"status": @"success", @"refId": refId, @"handle": path}]);
|
||||
RCT_EXPORT_METHOD(on:(NSString *) appName args:(NSDictionary *) args) {
|
||||
// todo
|
||||
// RNFirebaseDatabaseReference *ref = [self getInternalReferenceForApp:appName refId:refId path:path modifiers:modifiers keep:false];
|
||||
// TODO query identifier
|
||||
// [ref addEventHandler:listenerId eventName:eventName];
|
||||
}
|
||||
|
||||
/* TODO
|
||||
|
||||
RCT_EXPORT_METHOD(off:
|
||||
(nonnull
|
||||
NSNumber *) refId
|
||||
|
@ -432,9 +474,9 @@ RCT_EXPORT_METHOD(off:
|
|||
return ref;
|
||||
} */
|
||||
|
||||
// Not sure how to get away from this... yet
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];
|
||||
return @[DATABASE_ON_EVENT, DATABASE_CANCEL_EVENT, DATABASE_TRANSACTION_EVENT];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
if (!_listeners[listenerId]) {
|
||||
id andPreviousSiblingKeyWithBlock = ^(FIRDataSnapshot *_Nonnull snapshot, NSString *_Nullable previousChildName) {
|
||||
NSDictionary *props = [RNFirebaseDatabaseReference snapshotToDict:snapshot];
|
||||
[self sendJSEvent:DATABASE_DATA_EVENT title:eventName props:@{@"eventName": eventName, @"refId": _refId, @"listenerId": listenerId, @"path": _path, @"snapshot": props, @"previousChildName": previousChildName != nil ? previousChildName : [NSNull null]}];
|
||||
[self sendJSEvent:DATABASE_ON_EVENT title:eventName props:@{@"eventName": eventName, @"refId": _refId, @"listenerId": listenerId, @"path": _path, @"snapshot": props, @"previousChildName": previousChildName != nil ? previousChildName : [NSNull null]}];
|
||||
};
|
||||
id errorBlock = ^(NSError *_Nonnull error) {
|
||||
NSLog(@"Error onDBEvent: %@", [error debugDescription]);
|
||||
|
@ -130,13 +130,13 @@
|
|||
}
|
||||
|
||||
- (NSDictionary *)getAndSendDatabaseError:(NSError *)error listenerId:(NSNumber *)listenerId {
|
||||
NSDictionary *event = @{@"eventName": DATABASE_ERROR_EVENT, @"path": _path, @"refId": _refId, @"listenerId": listenerId, @"code": @([error code]), @"details": [error debugDescription], @"message": [error localizedDescription], @"description": [error description]};
|
||||
NSDictionary *event = @{@"eventName": DATABASE_CANCEL_EVENT, @"path": _path, @"refId": _refId, @"listenerId": listenerId, @"code": @([error code]), @"details": [error debugDescription], @"message": [error localizedDescription], @"description": [error description]};
|
||||
|
||||
@try {
|
||||
[_emitter sendEventWithName:DATABASE_ERROR_EVENT body:event];
|
||||
[_emitter sendEventWithName:DATABASE_CANCEL_EVENT body:event];
|
||||
} @catch (NSException *err) {
|
||||
NSLog(@"An error occurred in getAndSendDatabaseError: %@", [err debugDescription]);
|
||||
NSLog(@"Tried to send: %@ with %@", DATABASE_ERROR_EVENT, event);
|
||||
NSLog(@"Tried to send: %@ with %@", DATABASE_CANCEL_EVENT, event);
|
||||
}
|
||||
|
||||
return event;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import { NativeModules } from 'react-native';
|
||||
|
||||
import Reference from './reference';
|
||||
import Snapshot from './snapshot';
|
||||
import TransactionHandler from './transaction';
|
||||
import ModuleBase from './../../utils/ModuleBase';
|
||||
|
||||
|
@ -14,6 +15,7 @@ import ModuleBase from './../../utils/ModuleBase';
|
|||
export default class Database extends ModuleBase {
|
||||
constructor(firebaseApp: Object, options: Object = {}) {
|
||||
super(firebaseApp, options, 'Database', true);
|
||||
this._references = {};
|
||||
this._serverTimeOffset = 0;
|
||||
this._transactionHandler = new TransactionHandler(this);
|
||||
|
||||
|
@ -21,9 +23,49 @@ export default class Database extends ModuleBase {
|
|||
this._native.setPersistence(this._options.persistence);
|
||||
}
|
||||
|
||||
// todo event & error listeners
|
||||
// todo serverTimeOffset event/listener - make ref natively and switch to events
|
||||
// todo use nativeToJSError for on/off error events
|
||||
this.addListener(
|
||||
this._getAppEventName('database_cancel_event'),
|
||||
this._handleCancelEvent.bind(this),
|
||||
);
|
||||
|
||||
this.addListener(
|
||||
this._getAppEventName('database_on_event'),
|
||||
this._handleOnEvent.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
_handleOnEvent(event) {
|
||||
console.log('>>>ON-event>>>', event);
|
||||
const { queryKey, body, refId } = event;
|
||||
const { snapshot, previousChildName } = body;
|
||||
|
||||
const remainingListeners = this.listeners(queryKey);
|
||||
|
||||
if (!remainingListeners || !remainingListeners.length) {
|
||||
this._database._native.off(
|
||||
_refId,
|
||||
queryKey,
|
||||
);
|
||||
|
||||
delete this._references[refId];
|
||||
} else {
|
||||
const ref = this._references[refId];
|
||||
|
||||
if (!ref) {
|
||||
this._database._native.off(
|
||||
_refId,
|
||||
queryKey,
|
||||
);
|
||||
} else {
|
||||
this.emit(queryKey, new Snapshot(ref, snapshot), previousChildName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleCancelEvent(event) {
|
||||
console.log('>>>CANCEL-event>>>', event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
*/
|
||||
|
||||
import Reference from './reference.js';
|
||||
import { objectToUniqueId } from './../../utils';
|
||||
|
||||
// todo doc methods
|
||||
|
||||
/**
|
||||
* @class Query
|
||||
|
@ -15,6 +18,12 @@ export default class Query {
|
|||
this._reference = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param key
|
||||
* @return {Reference|*}
|
||||
*/
|
||||
orderBy(name: string, key?: string) {
|
||||
this.modifiers.push({
|
||||
type: 'orderBy',
|
||||
|
@ -25,6 +34,12 @@ export default class Query {
|
|||
return this._reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param limit
|
||||
* @return {Reference|*}
|
||||
*/
|
||||
limit(name: string, limit: number) {
|
||||
this.modifiers.push({
|
||||
type: 'limit',
|
||||
|
@ -35,6 +50,13 @@ export default class Query {
|
|||
return this._reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @param key
|
||||
* @return {Reference|*}
|
||||
*/
|
||||
filter(name: string, value: any, key?: string) {
|
||||
this.modifiers.push({
|
||||
type: 'filter',
|
||||
|
@ -47,7 +69,27 @@ export default class Query {
|
|||
return this._reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {[*]}
|
||||
*/
|
||||
getModifiers(): Array<DatabaseModifier> {
|
||||
return [...this.modifiers];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
queryIdentifier() {
|
||||
// convert query modifiers array into an object for generating a unique key
|
||||
const object = {};
|
||||
|
||||
for (let i = 0, len = this.modifiers.length; i < len; i++) {
|
||||
const { name, type, value } = this.modifiers[i];
|
||||
object[`${type}-${name}`] = value;
|
||||
}
|
||||
|
||||
return objectToUniqueId(object);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Query from './query.js';
|
||||
import Snapshot from './snapshot';
|
||||
import Disconnect from './disconnect';
|
||||
import INTERNALS from './../../internals';
|
||||
import ReferenceBase from './../../utils/ReferenceBase';
|
||||
import { promiseOrCallback, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils';
|
||||
import {
|
||||
promiseOrCallback,
|
||||
isFunction,
|
||||
isObject,
|
||||
isString,
|
||||
tryJSONParse,
|
||||
tryJSONStringify,
|
||||
generatePushID,
|
||||
} from './../../utils';
|
||||
|
||||
// Unique Reference ID for native events
|
||||
let refId = 1;
|
||||
|
@ -61,6 +71,7 @@ export default class Reference extends ReferenceBase {
|
|||
super(path, database);
|
||||
this._promise = null;
|
||||
this._refId = refId++;
|
||||
this._listeners = 0;
|
||||
this._refListeners = {};
|
||||
this._database = database;
|
||||
this._query = new Query(this, path, existingModifiers);
|
||||
|
@ -491,6 +502,17 @@ export default class Reference extends ReferenceBase {
|
|||
* INTERNALS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate a unique key based on this refs path and query modifiers
|
||||
* @return {string}
|
||||
*/
|
||||
makeQueryKey() {
|
||||
return `$${this.path}$${this._query.queryIdentifier()}$${this._refId}$${this._listeners}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return instance of db logger
|
||||
*/
|
||||
get log() {
|
||||
return this._database.log;
|
||||
}
|
||||
|
@ -540,219 +562,103 @@ export default class Reference extends ReferenceBase {
|
|||
}
|
||||
|
||||
|
||||
// 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.
|
||||
* @param eventType
|
||||
* @param callback
|
||||
* @param cancelCallback
|
||||
* @return {*}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
|
||||
let _failureCallback;
|
||||
let _context;
|
||||
|
||||
if (context) {
|
||||
_context = context;
|
||||
_failureCallback = failureCallbackOrContext;
|
||||
} else if (isFunction(failureCallbackOrContext)) {
|
||||
_failureCallback = failureCallbackOrContext;
|
||||
} else {
|
||||
_context = failureCallbackOrContext;
|
||||
// todo:context shouldn't be needed - confirm
|
||||
// todo refId should no longer be required - update native to work without it then remove from js internals
|
||||
on(eventType: string, callback: () => any, cancelCallback: () => any): Function {
|
||||
if (!eventType) {
|
||||
throw new Error('Query.on failed: Function called with 0 arguments. Expects at least 2.');
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
// brb, helping someone
|
||||
let _successCallback;
|
||||
|
||||
if (_context) {
|
||||
_successCallback = successCallback.bind(_context);
|
||||
} else {
|
||||
_successCallback = successCallback;
|
||||
if (!isString(eventType) || !ReferenceEventTypes[eventType]) {
|
||||
throw new Error(`Query.on failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
|
||||
}
|
||||
|
||||
const listener = {
|
||||
listenerId: Object.keys(this._refListeners).length + 1,
|
||||
eventName: eventType,
|
||||
successCallback: _successCallback,
|
||||
failureCallback: _failureCallback,
|
||||
};
|
||||
if (!callback) {
|
||||
throw new Error('Query.on failed: Function called with 1 argument. Expects at least 2.');
|
||||
}
|
||||
|
||||
this._refListeners[listener.listenerId] = listener;
|
||||
this._database.on(this, listener);
|
||||
return successCallback;
|
||||
if (!isFunction(callback)) {
|
||||
throw new Error('Query.on failed: Second argument must be a valid function.');
|
||||
}
|
||||
|
||||
if (cancelCallback && !isFunction(cancelCallback)) {
|
||||
throw new Error('Query.on failed: Function called with 3 arguments, but third optional argument `cancelCallback` was not a function.');
|
||||
}
|
||||
|
||||
const eventQueryKey = `${this.makeQueryKey()}$${eventType}`;
|
||||
|
||||
INTERNALS.SharedEventEmitter.addListener(eventQueryKey, callback);
|
||||
|
||||
if (isFunction(cancelCallback)) {
|
||||
INTERNALS.SharedEventEmitter.once(`${this.makeQueryKey()}:cancelled`, cancelCallback);
|
||||
}
|
||||
|
||||
// initialise the native listener if not already listening
|
||||
this._database._native.on({
|
||||
eventType,
|
||||
eventQueryKey,
|
||||
id: this._refId, // todo remove
|
||||
path: this.path,
|
||||
modifiers: this._query.getModifiers(),
|
||||
});
|
||||
|
||||
if (!this._database._references[this._refId]) {
|
||||
this._database._references[this._refId] = this;
|
||||
}
|
||||
|
||||
this._listeners = this._listeners + 1;
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @param eventType
|
||||
* @param originalCallback
|
||||
*/
|
||||
off(eventType?: string = '', originalCallback?: () => any) {
|
||||
// $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;
|
||||
if (eventType && (!isString(eventType) || !ReferenceEventTypes[eventType])) {
|
||||
throw new Error(`Query.off failed: First argument must be a valid string event type: "${Object.keys(ReferenceEventTypes).join(', ')}"`);
|
||||
}
|
||||
|
||||
if (originalCallback && !isFunction(originalCallback)) {
|
||||
throw new Error('Query.off failed: Function called with 2 arguments, but second optional argument was not a function.');
|
||||
}
|
||||
|
||||
if (eventType) {
|
||||
const eventQueryKey = `${this.makeQueryKey()}$${eventType}`;
|
||||
|
||||
if (originalCallback) {
|
||||
INTERNALS.SharedEventEmitter.removeListener(eventQueryKey, originalCallback);
|
||||
} else {
|
||||
INTERNALS.SharedEventEmitter.removeAllListeners(eventQueryKey);
|
||||
INTERNALS.SharedEventEmitter.removeAllListeners(`${this.makeQueryKey()}:cancelled`);
|
||||
}
|
||||
|
||||
// check if there's any listeners remaining in the js thread
|
||||
// if there's isn't then call the native .off method which
|
||||
// will unsubscribe from the native firebase listeners
|
||||
const remainingListeners = INTERNALS.SharedEventEmitter.listeners(eventQueryKey);
|
||||
|
||||
if (!remainingListeners || !remainingListeners.length) {
|
||||
this._database._native.off(
|
||||
this._refId, // todo remove
|
||||
eventQueryKey,
|
||||
);
|
||||
|
||||
// remove straggling cancellation listeners
|
||||
INTERNALS.SharedEventEmitter.removeAllListeners(`${this.makeQueryKey()}:cancelled`);
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ const NATIVE_MODULE_EVENTS = {
|
|||
'onAuthStateChanged',
|
||||
],
|
||||
Database: [
|
||||
'database_on_event',
|
||||
'database_cancel_event',
|
||||
'database_transaction_event',
|
||||
'database_event',
|
||||
'database_error',
|
||||
// 'database_server_offset', // TODO
|
||||
],
|
||||
};
|
||||
|
|
|
@ -17,9 +17,11 @@ const DEFAULT_CHUNK_SIZE = 50;
|
|||
* @param joiner
|
||||
* @returns {*}
|
||||
*/
|
||||
export function deepGet(object: Object,
|
||||
path: string,
|
||||
joiner?: string = '/'): any {
|
||||
export function deepGet(
|
||||
object: Object,
|
||||
path: string,
|
||||
joiner?: string = '/'
|
||||
): any {
|
||||
const keys = path.split(joiner);
|
||||
|
||||
let i = 0;
|
||||
|
@ -43,9 +45,11 @@ export function deepGet(object: Object,
|
|||
* @param joiner
|
||||
* @returns {*}
|
||||
*/
|
||||
export function deepExists(object: Object,
|
||||
path: string,
|
||||
joiner?: string = '/'): boolean {
|
||||
export function deepExists(
|
||||
object: Object,
|
||||
path: string,
|
||||
joiner?: string = '/'
|
||||
): boolean {
|
||||
const keys = path.split(joiner);
|
||||
|
||||
let i = 0;
|
||||
|
@ -133,10 +137,12 @@ export function noop(): void {
|
|||
* @param callback
|
||||
* @private
|
||||
*/
|
||||
function _delayChunk(collection: Array<*>,
|
||||
chunkSize: number,
|
||||
operation: Function,
|
||||
callback: Function): void {
|
||||
function _delayChunk(
|
||||
collection: Array<*>,
|
||||
chunkSize: number,
|
||||
operation: Function,
|
||||
callback: Function
|
||||
): void {
|
||||
const length = collection.length;
|
||||
const iterations = Math.ceil(length / chunkSize);
|
||||
|
||||
|
@ -164,10 +170,12 @@ function _delayChunk(collection: Array<*>,
|
|||
* @param iterator
|
||||
* @param cb
|
||||
*/
|
||||
export function each(array: Array<*>,
|
||||
chunkSize: number | Function,
|
||||
iterator: Function,
|
||||
cb?: Function): void {
|
||||
export function each(
|
||||
array: Array<*>,
|
||||
chunkSize: number | Function,
|
||||
iterator: Function,
|
||||
cb?: Function
|
||||
): void {
|
||||
if (typeof chunkSize === 'function') {
|
||||
cb = iterator;
|
||||
iterator = chunkSize;
|
||||
|
@ -202,10 +210,12 @@ export function typeOf(value: any): string {
|
|||
* @param cb
|
||||
* @returns {*}
|
||||
*/
|
||||
export function map(array: Array<*>,
|
||||
chunkSize: number | Function,
|
||||
iterator: Function,
|
||||
cb?: Function): void {
|
||||
export function map(
|
||||
array: Array<*>,
|
||||
chunkSize: number | Function,
|
||||
iterator: Function,
|
||||
cb?: Function
|
||||
): void {
|
||||
if (typeof chunkSize === 'function') {
|
||||
cb = iterator;
|
||||
iterator = chunkSize;
|
||||
|
@ -307,6 +317,24 @@ export function nativeWithApp(appName, NativeModule) {
|
|||
return native;
|
||||
}
|
||||
|
||||
export function objectToUniqueId(object: Object): String {
|
||||
if (!isObject(object) || object === null) return JSON.stringify(object);
|
||||
|
||||
const keys = Object.keys(object).sort();
|
||||
|
||||
let key = '{';
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (i !== 0) key += ',';
|
||||
key += JSON.stringify(keys[i]);
|
||||
key += ':';
|
||||
key += objectToUniqueId(object[keys[i]]);
|
||||
}
|
||||
|
||||
key += '}';
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the existing promise if no callback provided or
|
||||
* exec the promise and callback if optionalCallback is valid.
|
||||
|
|
|
@ -22,10 +22,11 @@ import priorityTests from './priorityTests';
|
|||
import DatabaseContents from '../../support/DatabaseContents';
|
||||
|
||||
const testGroups = [
|
||||
issueSpecificTests, factoryTests, keyTests, parentTests, childTests, rootTests,
|
||||
pushTests, onTests, onValueTests, onChildAddedTests, offTests, onceTests, updateTests,
|
||||
removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
|
||||
priorityTests,
|
||||
// issueSpecificTests, factoryTests, keyTests, parentTests, childTests, rootTests,
|
||||
// pushTests, onTests, onValueTests, onChildAddedTests, offTests, onceTests, updateTests,
|
||||
// removeTests, setTests, transactionTests, queryTests, refTests, isEqualTests,
|
||||
// priorityTests,
|
||||
onValueTests, onChildAddedTests, offTests,
|
||||
];
|
||||
|
||||
function registerTestSuite(testSuite) {
|
||||
|
|
|
@ -97,6 +97,7 @@ function onTests({ fdescribe, context, it, firebase, tryCatch }) {
|
|||
|
||||
await new Promise((resolve) => {
|
||||
ref.on('value', (snapshot) => {
|
||||
console.log('>>> SNAP',snapshot.val())
|
||||
callback(snapshot.val());
|
||||
resolve();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue