2017-03-09 15:26:28 +00:00
|
|
|
|
#import "RNFirebase.h"
|
|
|
|
|
#import "RNFirebaseDatabase.h"
|
|
|
|
|
#import "RNFirebaseEvents.h"
|
|
|
|
|
|
|
|
|
|
@interface RNFirebaseDBReference : NSObject
|
|
|
|
|
@property RCTEventEmitter *emitter;
|
|
|
|
|
@property FIRDatabaseQuery *query;
|
|
|
|
|
@property NSString *path;
|
|
|
|
|
@property NSString *modifiersString;
|
|
|
|
|
@property NSMutableDictionary *listeners;
|
|
|
|
|
@property FIRDatabaseHandle childAddedHandler;
|
|
|
|
|
@property FIRDatabaseHandle childModifiedHandler;
|
|
|
|
|
@property FIRDatabaseHandle childRemovedHandler;
|
|
|
|
|
@property FIRDatabaseHandle childMovedHandler;
|
|
|
|
|
@property FIRDatabaseHandle childValueHandler;
|
2017-03-25 00:59:27 +00:00
|
|
|
|
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot;
|
2017-03-09 15:26:28 +00:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation RNFirebaseDBReference
|
|
|
|
|
|
|
|
|
|
- (id) initWithPathAndModifiers:(RCTEventEmitter *) emitter
|
|
|
|
|
database:(FIRDatabase *) database
|
|
|
|
|
path:(NSString *) path
|
|
|
|
|
modifiers:(NSArray *) modifiers
|
|
|
|
|
modifiersString:(NSString *) modifiersString
|
|
|
|
|
{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
self = [super init];
|
|
|
|
|
if (self) {
|
|
|
|
|
_emitter = emitter;
|
|
|
|
|
_path = path;
|
|
|
|
|
_modifiersString = modifiersString;
|
|
|
|
|
_query = [self buildQueryAtPathWithModifiers:database path:path modifiers:modifiers];
|
|
|
|
|
_listeners = [[NSMutableDictionary alloc] init];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) addEventHandler:(NSString *) eventName
|
|
|
|
|
{
|
|
|
|
|
if (![self isListeningTo:eventName]) {
|
|
|
|
|
id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
|
2017-03-25 00:59:27 +00:00
|
|
|
|
NSDictionary *props = [RNFirebaseDBReference snapshotToDict:snapshot];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[self sendJSEvent:DATABASE_DATA_EVENT
|
|
|
|
|
title:eventName
|
|
|
|
|
props: @{
|
|
|
|
|
@"eventName": eventName,
|
|
|
|
|
@"path": _path,
|
|
|
|
|
@"modifiersString": _modifiersString,
|
|
|
|
|
@"snapshot": props
|
|
|
|
|
}];
|
|
|
|
|
};
|
|
|
|
|
id errorBlock = ^(NSError * _Nonnull error) {
|
|
|
|
|
NSLog(@"Error onDBEvent: %@", [error debugDescription]);
|
2017-03-09 16:46:02 +00:00
|
|
|
|
[self unsetListeningOn:eventName];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[self getAndSendDatabaseError:error
|
|
|
|
|
path:_path
|
|
|
|
|
modifiersString:_modifiersString];
|
|
|
|
|
};
|
|
|
|
|
int eventType = [self eventTypeFromName:eventName];
|
|
|
|
|
FIRDatabaseHandle handle = [_query observeEventType:eventType
|
|
|
|
|
withBlock:withBlock
|
|
|
|
|
withCancelBlock:errorBlock];
|
|
|
|
|
[self setEventHandler:handle forName:eventName];
|
|
|
|
|
} else {
|
|
|
|
|
NSLog(@"Warning Trying to add duplicate listener for type: %@ with modifiers: %@ for path: %@", eventName, _modifiersString, _path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) addSingleEventHandler:(RCTResponseSenderBlock) callback
|
|
|
|
|
{
|
|
|
|
|
[_query observeSingleEventOfType:FIRDataEventTypeValue
|
2017-03-25 00:59:27 +00:00
|
|
|
|
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
|
|
|
|
|
NSDictionary *props = [RNFirebaseDBReference snapshotToDict:snapshot];
|
|
|
|
|
callback(@[[NSNull null], @{
|
|
|
|
|
@"eventName": @"value",
|
|
|
|
|
@"path": _path,
|
|
|
|
|
@"modifiersString": _modifiersString,
|
|
|
|
|
@"snapshot": props
|
|
|
|
|
}]);
|
|
|
|
|
}
|
2017-03-09 15:26:28 +00:00
|
|
|
|
withCancelBlock:^(NSError * _Nonnull error) {
|
|
|
|
|
NSLog(@"Error onDBEventOnce: %@", [error debugDescription]);
|
|
|
|
|
callback(@[@{
|
|
|
|
|
@"eventName": DATABASE_ERROR_EVENT,
|
|
|
|
|
@"path": _path,
|
|
|
|
|
@"modifiers": _modifiersString,
|
|
|
|
|
@"code": @([error code]),
|
|
|
|
|
@"details": [error debugDescription],
|
|
|
|
|
@"message": [error localizedDescription],
|
|
|
|
|
@"description": [error description]
|
2017-03-25 00:59:27 +00:00
|
|
|
|
}]);
|
|
|
|
|
}];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) removeEventHandler:(NSString *) name
|
|
|
|
|
{
|
|
|
|
|
int eventType = [self eventTypeFromName:name];
|
|
|
|
|
switch (eventType) {
|
|
|
|
|
case FIRDataEventTypeValue:
|
|
|
|
|
if (self.childValueHandler != nil) {
|
|
|
|
|
[_query removeObserverWithHandle:self.childValueHandler];
|
|
|
|
|
self.childValueHandler = nil;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildAdded:
|
|
|
|
|
if (self.childAddedHandler != nil) {
|
|
|
|
|
[_query removeObserverWithHandle:self.childAddedHandler];
|
|
|
|
|
self.childAddedHandler = nil;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildChanged:
|
|
|
|
|
if (self.childModifiedHandler != nil) {
|
|
|
|
|
[_query removeObserverWithHandle:self.childModifiedHandler];
|
|
|
|
|
self.childModifiedHandler = nil;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildRemoved:
|
|
|
|
|
if (self.childRemovedHandler != nil) {
|
|
|
|
|
[_query removeObserverWithHandle:self.childRemovedHandler];
|
|
|
|
|
self.childRemovedHandler = nil;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildMoved:
|
|
|
|
|
if (self.childMovedHandler != nil) {
|
|
|
|
|
[_query removeObserverWithHandle:self.childMovedHandler];
|
|
|
|
|
self.childMovedHandler = nil;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
[self unsetListeningOn:name];
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-25 00:59:27 +00:00
|
|
|
|
+ (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot
|
2017-03-09 15:26:28 +00:00
|
|
|
|
{
|
|
|
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
|
|
|
[dict setValue:snapshot.key forKey:@"key"];
|
|
|
|
|
NSDictionary *val = snapshot.value;
|
|
|
|
|
[dict setObject:val forKey:@"value"];
|
|
|
|
|
// Snapshot ordering
|
|
|
|
|
NSMutableArray *childKeys = [NSMutableArray array];
|
|
|
|
|
if (snapshot.childrenCount > 0) {
|
|
|
|
|
// Since JS does not respect object ordering of keys
|
|
|
|
|
// we keep a list of the keys and their ordering
|
|
|
|
|
// in the snapshot event
|
|
|
|
|
NSEnumerator *children = [snapshot children];
|
|
|
|
|
FIRDataSnapshot *child;
|
|
|
|
|
while(child = [children nextObject]) {
|
|
|
|
|
[childKeys addObject:child.key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[dict setObject:childKeys forKey:@"childKeys"];
|
|
|
|
|
[dict setValue:@(snapshot.hasChildren) forKey:@"hasChildren"];
|
|
|
|
|
[dict setValue:@(snapshot.exists) forKey:@"exists"];
|
|
|
|
|
[dict setValue:@(snapshot.childrenCount) forKey:@"childrenCount"];
|
|
|
|
|
[dict setValue:snapshot.priority forKey:@"priority"];
|
|
|
|
|
return dict;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSDictionary *) getAndSendDatabaseError:(NSError *) error
|
|
|
|
|
path:(NSString *) path
|
|
|
|
|
modifiersString:(NSString *) modifiersString
|
|
|
|
|
{
|
|
|
|
|
NSDictionary *event = @{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
@"eventName": DATABASE_ERROR_EVENT,
|
|
|
|
|
@"path": path,
|
|
|
|
|
@"modifiers": modifiersString,
|
|
|
|
|
@"code": @([error code]),
|
|
|
|
|
@"details": [error debugDescription],
|
|
|
|
|
@"message": [error localizedDescription],
|
|
|
|
|
@"description": [error description]
|
|
|
|
|
};
|
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
// [self sendJSEvent:DATABASE_ERROR_EVENT title:DATABASE_ERROR_EVENT props: event];
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
@try {
|
|
|
|
|
[_emitter sendEventWithName:DATABASE_ERROR_EVENT body:event];
|
|
|
|
|
}
|
|
|
|
|
@catch (NSException *err) {
|
|
|
|
|
NSLog(@"An error occurred in getAndSendDatabaseError: %@", [err debugDescription]);
|
|
|
|
|
NSLog(@"Tried to send: %@ with %@", DATABASE_ERROR_EVENT, event);
|
|
|
|
|
}
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
return event;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-25 00:59:27 +00:00
|
|
|
|
- (void) sendJSEvent:(NSString *)type title:(NSString *)title props:(NSDictionary *)props {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
@try {
|
2017-03-25 00:59:27 +00:00
|
|
|
|
[_emitter sendEventWithName:type body:@{ @"eventName": title, @"body": props }];
|
|
|
|
|
} @catch (NSException *err) {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
|
|
|
|
|
NSLog(@"Tried to send: %@ with %@", title, props);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database
|
|
|
|
|
path:(NSString*) path
|
|
|
|
|
modifiers:(NSArray *) modifiers
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseQuery *query = [[database reference] child:path];
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
for (NSString *str in modifiers) {
|
|
|
|
|
if ([str isEqualToString:@"orderByKey"]) {
|
|
|
|
|
query = [query queryOrderedByKey];
|
|
|
|
|
} else if ([str isEqualToString:@"orderByPriority"]) {
|
|
|
|
|
query = [query queryOrderedByPriority];
|
|
|
|
|
} else if ([str isEqualToString:@"orderByValue"]) {
|
|
|
|
|
query = [query queryOrderedByValue];
|
|
|
|
|
} else if ([str containsString:@"orderByChild"]) {
|
|
|
|
|
NSArray *args = [str componentsSeparatedByString:@":"];
|
|
|
|
|
NSString *key = args[1];
|
|
|
|
|
query = [query queryOrderedByChild:key];
|
|
|
|
|
} else if ([str containsString:@"limitToLast"]) {
|
|
|
|
|
NSArray *args = [str componentsSeparatedByString:@":"];
|
|
|
|
|
NSString *key = args[1];
|
|
|
|
|
NSUInteger limit = key.integerValue;
|
|
|
|
|
query = [query queryLimitedToLast:limit];
|
|
|
|
|
} else if ([str containsString:@"limitToFirst"]) {
|
|
|
|
|
NSArray *args = [str componentsSeparatedByString:@":"];
|
|
|
|
|
NSString *key = args[1];
|
|
|
|
|
NSUInteger limit = key.integerValue;
|
|
|
|
|
query = [query queryLimitedToFirst:limit];
|
|
|
|
|
} else if ([str containsString:@"equalTo"]) {
|
|
|
|
|
NSArray *args = [str componentsSeparatedByString:@":"];
|
|
|
|
|
int size = (int)[args count];;
|
|
|
|
|
id value = [self getIdValue:args[1] type:args[2]];
|
|
|
|
|
if (size > 3) {
|
|
|
|
|
NSString *key = args[3];
|
|
|
|
|
query = [query queryEqualToValue:value
|
|
|
|
|
childKey:key];
|
|
|
|
|
} else {
|
|
|
|
|
query = [query queryEqualToValue:value];
|
|
|
|
|
}
|
|
|
|
|
} else if ([str containsString:@"endAt"]) {
|
|
|
|
|
NSArray *args = [str componentsSeparatedByString:@":"];
|
|
|
|
|
int size = (int)[args count];;
|
|
|
|
|
id value = [self getIdValue:args[1] type:args[2]];
|
|
|
|
|
if (size > 3) {
|
|
|
|
|
NSString *key = args[3];
|
|
|
|
|
query = [query queryEndingAtValue:value
|
|
|
|
|
childKey:key];
|
|
|
|
|
} else {
|
|
|
|
|
query = [query queryEndingAtValue:value];
|
|
|
|
|
}
|
|
|
|
|
} else if ([str containsString:@"startAt"]) {
|
|
|
|
|
NSArray *args = [str componentsSeparatedByString:@":"];
|
|
|
|
|
id value = [self getIdValue:args[1] type:args[2]];
|
|
|
|
|
int size = (int)[args count];;
|
|
|
|
|
if (size > 3) {
|
|
|
|
|
NSString *key = args[3];
|
|
|
|
|
query = [query queryStartingAtValue:value
|
|
|
|
|
childKey:key];
|
|
|
|
|
} else {
|
|
|
|
|
query = [query queryStartingAtValue:value];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) getIdValue:(NSString *) value
|
|
|
|
|
type:(NSString *) type
|
|
|
|
|
{
|
|
|
|
|
if ([type isEqualToString:@"number"]) {
|
|
|
|
|
return [NSNumber numberWithInteger:value.integerValue];
|
|
|
|
|
} else if ([type isEqualToString:@"boolean"]) {
|
|
|
|
|
return [NSNumber numberWithBool:value.boolValue];
|
|
|
|
|
} else {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setEventHandler:(FIRDatabaseHandle) handle
|
|
|
|
|
forName:(NSString *) name
|
|
|
|
|
{
|
|
|
|
|
int eventType = [self eventTypeFromName:name];
|
|
|
|
|
switch (eventType) {
|
|
|
|
|
case FIRDataEventTypeValue:
|
|
|
|
|
self.childValueHandler = handle;
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildAdded:
|
|
|
|
|
self.childAddedHandler = handle;
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildChanged:
|
|
|
|
|
self.childModifiedHandler = handle;
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildRemoved:
|
|
|
|
|
self.childRemovedHandler = handle;
|
|
|
|
|
break;
|
|
|
|
|
case FIRDataEventTypeChildMoved:
|
|
|
|
|
self.childMovedHandler = handle;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
[self setListeningOn:name withHandle:handle];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) setListeningOn:(NSString *) name
|
|
|
|
|
withHandle:(FIRDatabaseHandle) handle
|
|
|
|
|
{
|
|
|
|
|
[_listeners setValue:@(handle) forKey:name];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) unsetListeningOn:(NSString *) name
|
|
|
|
|
{
|
|
|
|
|
[_listeners removeObjectForKey:name];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isListeningTo:(NSString *) name
|
|
|
|
|
{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
return [_listeners valueForKey:name] != nil;
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) hasListeners
|
|
|
|
|
{
|
|
|
|
|
return [[_listeners allKeys] count] > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray *) listenerKeys
|
|
|
|
|
{
|
|
|
|
|
return [_listeners allKeys];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (int) eventTypeFromName:(NSString *)name
|
|
|
|
|
{
|
|
|
|
|
int eventType = FIRDataEventTypeValue;
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if ([name isEqualToString:DATABASE_VALUE_EVENT]) {
|
|
|
|
|
eventType = FIRDataEventTypeValue;
|
|
|
|
|
} else if ([name isEqualToString:DATABASE_CHILD_ADDED_EVENT]) {
|
|
|
|
|
eventType = FIRDataEventTypeChildAdded;
|
|
|
|
|
} else if ([name isEqualToString:DATABASE_CHILD_MODIFIED_EVENT]) {
|
|
|
|
|
eventType = FIRDataEventTypeChildChanged;
|
|
|
|
|
} else if ([name isEqualToString:DATABASE_CHILD_REMOVED_EVENT]) {
|
|
|
|
|
eventType = FIRDataEventTypeChildRemoved;
|
|
|
|
|
} else if ([name isEqualToString:DATABASE_CHILD_MOVED_EVENT]) {
|
|
|
|
|
eventType = FIRDataEventTypeChildMoved;
|
|
|
|
|
}
|
|
|
|
|
return eventType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) cleanup {
|
|
|
|
|
if (self.childValueHandler > 0) {
|
|
|
|
|
[self removeEventHandler:DATABASE_VALUE_EVENT];
|
|
|
|
|
}
|
|
|
|
|
if (self.childAddedHandler > 0) {
|
|
|
|
|
[self removeEventHandler:DATABASE_CHILD_ADDED_EVENT];
|
|
|
|
|
}
|
|
|
|
|
if (self.childModifiedHandler > 0) {
|
|
|
|
|
[self removeEventHandler:DATABASE_CHILD_MODIFIED_EVENT];
|
|
|
|
|
}
|
|
|
|
|
if (self.childRemovedHandler > 0) {
|
|
|
|
|
[self removeEventHandler:DATABASE_CHILD_REMOVED_EVENT];
|
|
|
|
|
}
|
|
|
|
|
if (self.childMovedHandler > 0) {
|
|
|
|
|
[self removeEventHandler:DATABASE_CHILD_MOVED_EVENT];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
@implementation RNFirebaseDatabase
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_MODULE(RNFirebaseDatabase);
|
|
|
|
|
|
2017-03-25 00:59:27 +00:00
|
|
|
|
- (id) init {
|
2017-03-09 15:26:28 +00:00
|
|
|
|
self = [super init];
|
|
|
|
|
if (self != nil) {
|
|
|
|
|
_dbReferences = [[NSMutableDictionary alloc] init];
|
2017-03-25 00:59:27 +00:00
|
|
|
|
_transactions = [[NSMutableDictionary alloc] init];
|
|
|
|
|
_transactionQueue = dispatch_queue_create("io.invertase.react-native-firebase", DISPATCH_QUEUE_CONCURRENT);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-25 00:59:27 +00:00
|
|
|
|
- (void) sendTransactionEvent:(NSString *)type body:(id)body {
|
|
|
|
|
@try {
|
|
|
|
|
[self sendEventWithName:type body:body];
|
|
|
|
|
} @catch (NSException *err) {
|
|
|
|
|
NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]);
|
|
|
|
|
NSLog(@"Tried to send: %@ with %@", type, body);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(startTransaction:(NSString *) path identifier:(NSString *) identifier applyLocally:(BOOL) applyLocally) {
|
|
|
|
|
dispatch_async(_transactionQueue, ^{
|
|
|
|
|
NSMutableDictionary *transactionState = [NSMutableDictionary new];
|
|
|
|
|
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
|
|
|
|
[transactionState setObject:sema forKey:@"semaphore"];
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
|
|
|
|
|
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
|
|
|
|
|
dispatch_barrier_async(_transactionQueue, ^{
|
|
|
|
|
[_transactions setValue:transactionState forKey:identifier];
|
|
|
|
|
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{ @"id": identifier, @"type": @"update", @"value": currentData.value }];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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"];
|
|
|
|
|
|
|
|
|
|
dispatch_barrier_async(_transactionQueue, ^{
|
|
|
|
|
[_transactions removeObjectForKey:identifier];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (abort) {
|
|
|
|
|
return [FIRTransactionResult abort];
|
|
|
|
|
} else {
|
|
|
|
|
currentData.value = value;
|
|
|
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
|
|
|
}
|
|
|
|
|
} andCompletionBlock:^(NSError * _Nullable databaseError, BOOL committed, FIRDataSnapshot * _Nullable snapshot) {
|
|
|
|
|
if (databaseError != nil) {
|
|
|
|
|
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{
|
|
|
|
|
@"id": identifier,
|
|
|
|
|
@"type": @"error",
|
|
|
|
|
@"code": [NSNumber numberWithInt:[databaseError code]],
|
|
|
|
|
@"message": [databaseError description]
|
|
|
|
|
}];
|
|
|
|
|
} else {
|
|
|
|
|
[self sendTransactionEvent:DATABASE_TRANSACTION_EVENT body:@{
|
|
|
|
|
@"id": identifier,
|
|
|
|
|
@"type": @"complete",
|
|
|
|
|
@"committed": @(committed),
|
|
|
|
|
@"snapshot": [RNFirebaseDBReference snapshotToDict:snapshot],
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
} withLocalEvents:applyLocally];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(tryCommitTransaction:(NSString *) identifier withData:(NSDictionary *) data) {
|
|
|
|
|
__block NSMutableDictionary *transactionState;
|
|
|
|
|
|
|
|
|
|
dispatch_sync(_transactionQueue, ^{
|
|
|
|
|
transactionState = [_transactions objectForKey: identifier];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!transactionState) {
|
|
|
|
|
NSLog(@"tryCommitTransaction for unknown ID %@", identifier);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dispatch_semaphore_t sema = [transactionState valueForKey:@"semaphore"];
|
|
|
|
|
|
|
|
|
|
BOOL abort = [[data valueForKey:@"abort"] boolValue];
|
|
|
|
|
|
|
|
|
|
if (abort) {
|
|
|
|
|
[transactionState setValue:@true forKey:@"abort"];
|
|
|
|
|
} else {
|
|
|
|
|
id newValue = [data valueForKey:@"value"];
|
|
|
|
|
[transactionState setValue:newValue forKey:@"value"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dispatch_semaphore_signal(sema);
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
RCT_EXPORT_METHOD(enablePersistence:(BOOL) enable
|
2017-03-25 00:59:27 +00:00
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
2017-03-09 15:26:28 +00:00
|
|
|
|
{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
BOOL isEnabled = [FIRDatabase database].persistenceEnabled;
|
|
|
|
|
if ( isEnabled != enable) {
|
|
|
|
|
[FIRDatabase database].persistenceEnabled = enable;
|
|
|
|
|
}
|
|
|
|
|
callback(@[[NSNull null], @{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
@"result": @"success"
|
|
|
|
|
}]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(keepSynced:(NSString *) path
|
2017-03-25 00:59:27 +00:00
|
|
|
|
withEnable:(BOOL) enable
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
2017-03-09 15:26:28 +00:00
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref keepSynced:enable];
|
|
|
|
|
callback(@[[NSNull null], @{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
@"status": @"success",
|
|
|
|
|
@"path": path
|
|
|
|
|
}]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(set:(NSString *) path
|
|
|
|
|
data:(NSDictionary *)data
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref setValue:[data valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"set" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(update:(NSString *) path
|
|
|
|
|
value:(NSDictionary *)value
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref updateChildValues:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"update" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(remove:(NSString *) path
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref removeValueWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"remove" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(push:(NSString *) path
|
|
|
|
|
data:(NSDictionary *) data
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
FIRDatabaseReference *newRef = [ref childByAutoId];
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
NSURL *url = [NSURL URLWithString:newRef.URL];
|
|
|
|
|
NSString *newPath = [url path];
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if ([data count] > 0) {
|
|
|
|
|
[newRef setValue:[data valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
if (error != nil) {
|
|
|
|
|
// Error handling
|
|
|
|
|
NSDictionary *evt = @{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
@"code": @([error code]),
|
|
|
|
|
@"details": [error debugDescription],
|
|
|
|
|
@"message": [error localizedDescription],
|
|
|
|
|
@"description": [error description]
|
|
|
|
|
};
|
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
callback(@[evt]);
|
|
|
|
|
} else {
|
|
|
|
|
callback(@[[NSNull null], @{
|
|
|
|
|
@"status": @"success",
|
|
|
|
|
@"ref": newPath
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
}];
|
|
|
|
|
} else {
|
|
|
|
|
callback(@[[NSNull null], @{
|
2017-03-25 00:59:27 +00:00
|
|
|
|
@"status": @"success",
|
|
|
|
|
@"ref": newPath
|
|
|
|
|
}]);
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(on:(NSString *) path
|
|
|
|
|
modifiersString:(NSString *) modifiersString
|
|
|
|
|
modifiers:(NSArray *) modifiers
|
|
|
|
|
name:(NSString *) eventName
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
RNFirebaseDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString];
|
|
|
|
|
[ref addEventHandler:eventName];
|
|
|
|
|
callback(@[[NSNull null], @{
|
|
|
|
|
@"status": @"success",
|
|
|
|
|
@"handle": path
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(once:(NSString *) path
|
2017-03-25 00:59:27 +00:00
|
|
|
|
modifiersString:(NSString *) modifiersString
|
|
|
|
|
modifiers:(NSArray *) modifiers
|
|
|
|
|
name:(NSString *) name
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
2017-03-09 15:26:28 +00:00
|
|
|
|
{
|
|
|
|
|
RNFirebaseDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString];
|
|
|
|
|
[ref addSingleEventHandler:callback];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(off:(NSString *)path
|
|
|
|
|
modifiersString:(NSString *) modifiersString
|
|
|
|
|
eventName:(NSString *) eventName
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
NSString *key = [self getDBListenerKey:path withModifiers:modifiersString];
|
|
|
|
|
NSArray *listenerKeys;
|
|
|
|
|
RNFirebaseDBReference *ref = [_dbReferences objectForKey:key];
|
|
|
|
|
if (ref == nil) {
|
|
|
|
|
listenerKeys = @[];
|
|
|
|
|
} else {
|
|
|
|
|
if (eventName == nil || [eventName isEqualToString:@""]) {
|
|
|
|
|
[ref cleanup];
|
|
|
|
|
[_dbReferences removeObjectForKey:key];
|
|
|
|
|
} else {
|
|
|
|
|
[ref removeEventHandler:eventName];
|
|
|
|
|
if (![ref hasListeners]) {
|
|
|
|
|
[_dbReferences removeObjectForKey:key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
listenerKeys = [ref listenerKeys];
|
|
|
|
|
}
|
|
|
|
|
callback(@[[NSNull null], @{
|
|
|
|
|
@"result": @"success",
|
|
|
|
|
@"handle": path,
|
|
|
|
|
@"modifiersString": modifiersString,
|
|
|
|
|
@"remainingListeners": listenerKeys,
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// On disconnect
|
|
|
|
|
RCT_EXPORT_METHOD(onDisconnectSetObject:(NSString *) path
|
|
|
|
|
props:(NSDictionary *) props
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref onDisconnectSetValue:props
|
|
|
|
|
withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"onDisconnectSetObject" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(onDisconnectSetString:(NSString *) path
|
|
|
|
|
val:(NSString *) val
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref onDisconnectSetValue:val
|
|
|
|
|
withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"onDisconnectSetString" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) path
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref onDisconnectRemoveValueWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"onDisconnectRemove" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *) path
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback)
|
|
|
|
|
{
|
|
|
|
|
FIRDatabaseReference *ref = [self getPathRef:path];
|
|
|
|
|
[ref cancelDisconnectOperationsWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) {
|
|
|
|
|
[self handleCallback:@"onDisconnectCancel" callback:callback databaseError:error];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(goOffline)
|
|
|
|
|
{
|
|
|
|
|
[FIRDatabase database].goOffline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(goOnline)
|
|
|
|
|
{
|
|
|
|
|
[FIRDatabase database].goOnline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FIRDatabaseReference *) getPathRef:(NSString *) path
|
|
|
|
|
{
|
|
|
|
|
return [[[FIRDatabase database] reference] child:path];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) handleCallback:(NSString *) methodName
|
|
|
|
|
callback:(RCTResponseSenderBlock) callback
|
|
|
|
|
databaseError:(NSError *) databaseError
|
|
|
|
|
{
|
|
|
|
|
if (databaseError != nil) {
|
|
|
|
|
NSDictionary *evt = @{
|
|
|
|
|
@"code": [NSNumber numberWithInt:[databaseError code]],
|
|
|
|
|
@"details": [databaseError debugDescription],
|
|
|
|
|
@"message": [databaseError localizedDescription],
|
|
|
|
|
@"description": [databaseError description]
|
|
|
|
|
};
|
|
|
|
|
callback(@[evt]);
|
|
|
|
|
} else {
|
|
|
|
|
callback(@[[NSNull null], @{
|
|
|
|
|
@"status": @"success",
|
|
|
|
|
@"method": methodName
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (RNFirebaseDBReference *) getDBHandle:(NSString *) path
|
2017-03-25 00:59:27 +00:00
|
|
|
|
modifiers:modifiers
|
|
|
|
|
modifiersString:modifiersString
|
2017-03-09 15:26:28 +00:00
|
|
|
|
{
|
|
|
|
|
NSString *key = [self getDBListenerKey:path withModifiers:modifiersString];
|
|
|
|
|
RNFirebaseDBReference *ref = [_dbReferences objectForKey:key];
|
2017-03-25 00:59:27 +00:00
|
|
|
|
|
2017-03-09 15:26:28 +00:00
|
|
|
|
if (ref == nil) {
|
|
|
|
|
ref = [[RNFirebaseDBReference alloc] initWithPathAndModifiers:self
|
2017-03-25 00:59:27 +00:00
|
|
|
|
database:[FIRDatabase database]
|
|
|
|
|
path:path
|
|
|
|
|
modifiers:modifiers
|
|
|
|
|
modifiersString:modifiersString];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
[_dbReferences setObject:ref forKey:key];
|
|
|
|
|
}
|
|
|
|
|
return ref;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *) getDBListenerKey:(NSString *) path
|
|
|
|
|
withModifiers:(NSString *) modifiersString
|
|
|
|
|
{
|
|
|
|
|
return [NSString stringWithFormat:@"%@ | %@", path, modifiersString, nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Not sure how to get away from this... yet
|
|
|
|
|
- (NSArray<NSString *> *)supportedEvents {
|
2017-03-25 00:59:27 +00:00
|
|
|
|
return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT];
|
2017-03-09 15:26:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@end
|