@@ -21,6 +21,8 @@ @interface FirestackDBReference : NSObject
2121@property FIRDatabaseHandle childRemovedHandler;
2222@property FIRDatabaseHandle childMovedHandler;
2323@property FIRDatabaseHandle childValueHandler;
24+ + (NSDictionary *) snapshotToDict : (FIRDataSnapshot *) snapshot ;
25+
2426@end
2527
2628@implementation FirestackDBReference
@@ -52,7 +54,7 @@ - (void) addEventHandler:(NSString *) eventName
5254{
5355 if (![self isListeningTo: eventName]) {
5456 id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
55- NSDictionary *props = [self snapshotToDict: snapshot];
57+ NSDictionary *props = [FirestackDBReference snapshotToDict: snapshot];
5658 [self sendJSEvent: DATABASE_DATA_EVENT
5759 title: eventName
5860 props: @{
@@ -142,7 +144,7 @@ - (void) removeEventHandler:(NSString *) name
142144 [self unsetListeningOn: name];
143145}
144146
145- - (NSDictionary *) snapshotToDict : (FIRDataSnapshot *) snapshot
147+ + (NSDictionary *) snapshotToDict : (FIRDataSnapshot *) snapshot
146148{
147149 NSMutableDictionary *dict = [[NSMutableDictionary alloc ] init ];
148150 [dict setValue: snapshot.key forKey: @" key" ];
@@ -377,6 +379,8 @@ - (id) init
377379 self = [super init ];
378380 if (self != nil ) {
379381 _dbReferences = [[NSMutableDictionary alloc ] init ];
382+ _transactions = [[NSMutableDictionary alloc ] init ];
383+ _transactionQueue = dispatch_queue_create (" com.fullstackreact.react-native-firestack" , DISPATCH_QUEUE_CONCURRENT);
380384 }
381385 return self;
382386}
@@ -479,7 +483,85 @@ - (id) init
479483 }
480484}
481485
486+ RCT_EXPORT_METHOD (beginTransaction:(NSString *) path
487+ withIdentifier:(NSString *) identifier
488+ applyLocally:(BOOL ) applyLocally
489+ onComplete:(RCTResponseSenderBlock) onComplete)
490+ {
491+ dispatch_async (_transactionQueue, ^{
492+ NSMutableDictionary *transactionState = [NSMutableDictionary new ];
493+
494+ dispatch_semaphore_t sema = dispatch_semaphore_create (0 );
495+ [transactionState setObject: sema forKey: @" semaphore" ];
496+
497+ FIRDatabaseReference *ref = [self getPathRef: path];
498+ [ref runTransactionBlock: ^FIRTransactionResult * _Nonnull (FIRMutableData * _Nonnull currentData) {
499+ dispatch_barrier_async (_transactionQueue, ^{
500+ [_transactions setValue: transactionState forKey: identifier];
501+ [self sendEventWithName: DATABASE_TRANSACTION_EVENT
502+ body: @{
503+ @" id" : identifier,
504+ @" originalValue" : currentData.value
505+ }];
506+ });
507+ // Wait for the event handler to call tryCommitTransaction
508+ // WARNING: This wait occurs on the Firebase Worker Queue
509+ // so if tryCommitTransaction fails to signal the semaphore
510+ // no further blocks will be executed by Firebase until the timeout expires
511+ dispatch_time_t delayTime = dispatch_time (DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC);
512+ BOOL timedout = dispatch_semaphore_wait (sema, delayTime) != 0 ;
513+ BOOL abort = [transactionState valueForKey: @" abort" ] || timedout;
514+ id value = [transactionState valueForKey: @" value" ];
515+ dispatch_barrier_async (_transactionQueue, ^{
516+ [_transactions removeObjectForKey: identifier];
517+ });
518+ if (abort) {
519+ return [FIRTransactionResult abort ];
520+ } else {
521+ currentData.value = value;
522+ return [FIRTransactionResult successWithValue: currentData];
523+ }
524+ } andCompletionBlock: ^(NSError * _Nullable databaseError, BOOL committed, FIRDataSnapshot * _Nullable snapshot) {
525+ if (databaseError != nil ) {
526+ NSDictionary *evt = @{
527+ @" errorCode" : [NSNumber numberWithInt: [databaseError code ]],
528+ @" errorDetails" : [databaseError debugDescription ],
529+ @" description" : [databaseError description ]
530+ };
531+ onComplete (@[evt]);
532+ } else {
533+ onComplete (@[[NSNull null ], @{
534+ @" committed" : [NSNumber numberWithBool: committed],
535+ @" snapshot" : [FirestackDBReference snapshotToDict: snapshot],
536+ @" status" : @" success" ,
537+ @" method" : @" transaction"
538+ }]);
539+ }
540+ } withLocalEvents: applyLocally];
541+ });
542+ }
482543
544+ RCT_EXPORT_METHOD (tryCommitTransaction:(NSString *) identifier
545+ withData:(NSDictionary *) data
546+ orAbort:(BOOL ) abort)
547+ {
548+ __block NSMutableDictionary *transactionState;
549+ dispatch_sync (_transactionQueue, ^{
550+ transactionState = [_transactions objectForKey: identifier];
551+ });
552+ if (!transactionState) {
553+ NSLog (@" tryCommitTransaction for unknown ID %@ " , identifier);
554+ return ;
555+ }
556+ dispatch_semaphore_t sema = [transactionState valueForKey: @" semaphore" ];
557+ if (abort) {
558+ [transactionState setValue: @true forKey: @" abort" ];
559+ } else {
560+ id newValue = [data valueForKey: @" value" ];
561+ [transactionState setValue: newValue forKey: @" value" ];
562+ }
563+ dispatch_semaphore_signal (sema);
564+ }
483565
484566RCT_EXPORT_METHOD (on:(NSString *) path
485567 modifiersString:(NSString *) modifiersString
@@ -634,7 +716,7 @@ - (NSString *) getDBListenerKey:(NSString *) path
634716
635717// Not sure how to get away from this... yet
636718- (NSArray <NSString *> *)supportedEvents {
637- return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT];
719+ return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT ];
638720}
639721
640722
0 commit comments