@@ -21,6 +21,8 @@ @interface FirestackDBReference : NSObject
21
21
@property FIRDatabaseHandle childRemovedHandler;
22
22
@property FIRDatabaseHandle childMovedHandler;
23
23
@property FIRDatabaseHandle childValueHandler;
24
+ + (NSDictionary *) snapshotToDict : (FIRDataSnapshot *) snapshot ;
25
+
24
26
@end
25
27
26
28
@implementation FirestackDBReference
@@ -52,7 +54,7 @@ - (void) addEventHandler:(NSString *) eventName
52
54
{
53
55
if (![self isListeningTo: eventName]) {
54
56
id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) {
55
- NSDictionary *props = [self snapshotToDict: snapshot];
57
+ NSDictionary *props = [FirestackDBReference snapshotToDict: snapshot];
56
58
[self sendJSEvent: DATABASE_DATA_EVENT
57
59
title: eventName
58
60
props: @{
@@ -142,7 +144,7 @@ - (void) removeEventHandler:(NSString *) name
142
144
[self unsetListeningOn: name];
143
145
}
144
146
145
- - (NSDictionary *) snapshotToDict : (FIRDataSnapshot *) snapshot
147
+ + (NSDictionary *) snapshotToDict : (FIRDataSnapshot *) snapshot
146
148
{
147
149
NSMutableDictionary *dict = [[NSMutableDictionary alloc ] init ];
148
150
[dict setValue: snapshot.key forKey: @" key" ];
@@ -377,6 +379,8 @@ - (id) init
377
379
self = [super init ];
378
380
if (self != nil ) {
379
381
_dbReferences = [[NSMutableDictionary alloc ] init ];
382
+ _transactions = [[NSMutableDictionary alloc ] init ];
383
+ _transactionQueue = dispatch_queue_create (" com.fullstackreact.react-native-firestack" , DISPATCH_QUEUE_CONCURRENT);
380
384
}
381
385
return self;
382
386
}
@@ -479,7 +483,85 @@ - (id) init
479
483
}
480
484
}
481
485
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
+ }
482
543
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
+ }
483
565
484
566
RCT_EXPORT_METHOD (on:(NSString *) path
485
567
modifiersString:(NSString *) modifiersString
@@ -634,7 +716,7 @@ - (NSString *) getDBListenerKey:(NSString *) path
634
716
635
717
// Not sure how to get away from this... yet
636
718
- (NSArray <NSString *> *)supportedEvents {
637
- return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT];
719
+ return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT, DATABASE_TRANSACTION_EVENT ];
638
720
}
639
721
640
722
0 commit comments