FPersistentConnection.m 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <Foundation/Foundation.h>
  17. #import <SystemConfiguration/SystemConfiguration.h>
  18. #import <netinet/in.h>
  19. #import <dlfcn.h>
  20. #import "FIRDatabaseReference.h"
  21. #import "FPersistentConnection.h"
  22. #import "FConstants.h"
  23. #import "FAtomicNumber.h"
  24. #import "FQueryParams.h"
  25. #import "FTupleOnDisconnect.h"
  26. #import "FTupleCallbackStatus.h"
  27. #import "FQuerySpec.h"
  28. #import "FIndex.h"
  29. #import "FIRDatabaseConfig.h"
  30. #import "FIRDatabaseConfig_Private.h"
  31. #import "FSnapshotUtilities.h"
  32. #import "FRangeMerge.h"
  33. #import "FCompoundHash.h"
  34. #import "FSyncTree.h"
  35. #import "FIRRetryHelper.h"
  36. #import "FAuthTokenProvider.h"
  37. #import "FUtilities.h"
  38. @interface FOutstandingQuery : NSObject
  39. @property (nonatomic, strong) FQuerySpec* query;
  40. @property (nonatomic, strong) NSNumber *tagId;
  41. @property (nonatomic, strong) id<FSyncTreeHash> syncTreeHash;
  42. @property (nonatomic, copy) fbt_void_nsstring onComplete;
  43. @end
  44. @implementation FOutstandingQuery
  45. @end
  46. @interface FOutstandingPut : NSObject
  47. @property (nonatomic, strong) NSString *action;
  48. @property (nonatomic, strong) NSDictionary *request;
  49. @property (nonatomic, copy) fbt_void_nsstring_nsstring onCompleteBlock;
  50. @property (nonatomic) BOOL sent;
  51. @end
  52. @implementation FOutstandingPut
  53. @end
  54. typedef enum {
  55. ConnectionStateDisconnected,
  56. ConnectionStateGettingToken,
  57. ConnectionStateConnecting,
  58. ConnectionStateAuthenticating,
  59. ConnectionStateConnected
  60. } ConnectionState;
  61. @interface FPersistentConnection () {
  62. ConnectionState connectionState;
  63. BOOL firstConnection;
  64. NSTimeInterval reconnectDelay;
  65. NSTimeInterval lastConnectionAttemptTime;
  66. NSTimeInterval lastConnectionEstablishedTime;
  67. SCNetworkReachabilityRef reachability;
  68. }
  69. - (int) getNextRequestNumber;
  70. - (void) onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body;
  71. - (void) handleTimestamp:(NSNumber *)timestamp;
  72. - (void) sendOnDisconnectAction:(NSString *)action forPath:(NSString *)pathString withData:(id)data andCallback:(fbt_void_nsstring_nsstring)callback;
  73. @property (nonatomic, strong) FConnection* realtime;
  74. @property (nonatomic, strong) NSMutableDictionary* listens;
  75. @property (nonatomic, strong) NSMutableDictionary* outstandingPuts;
  76. @property (nonatomic, strong) NSMutableArray* onDisconnectQueue;
  77. @property (nonatomic, strong) FRepoInfo* repoInfo;
  78. @property (nonatomic, strong) FAtomicNumber* putCounter;
  79. @property (nonatomic, strong) FAtomicNumber* requestNumber;
  80. @property (nonatomic, strong) NSMutableDictionary* requestCBHash;
  81. @property (nonatomic, strong) FIRDatabaseConfig *config;
  82. @property (nonatomic) NSUInteger unackedListensCount;
  83. @property (nonatomic, strong) NSMutableArray *putsToAck;
  84. @property (nonatomic, strong) dispatch_queue_t dispatchQueue;
  85. @property (nonatomic, strong) NSString* lastSessionID;
  86. @property (nonatomic, strong) NSMutableSet *interruptReasons;
  87. @property (nonatomic, strong) FIRRetryHelper *retryHelper;
  88. @property (nonatomic, strong) id<FAuthTokenProvider> authTokenProvider;
  89. @property (nonatomic, strong) NSString *authToken;
  90. @property (nonatomic) BOOL forceAuthTokenRefresh;
  91. @property (nonatomic) NSUInteger currentFetchTokenAttempt;
  92. @end
  93. @implementation FPersistentConnection
  94. - (id)initWithRepoInfo:(FRepoInfo *)repoInfo dispatchQueue:(dispatch_queue_t)dispatchQueue config:(FIRDatabaseConfig *)config {
  95. self = [super init];
  96. if (self) {
  97. self->_config = config;
  98. self->_repoInfo = repoInfo;
  99. self->_dispatchQueue = dispatchQueue;
  100. self->_authTokenProvider = config.authTokenProvider;
  101. NSAssert(self->_authTokenProvider != nil, @"Expected auth token provider");
  102. self.interruptReasons = [NSMutableSet set];
  103. self.listens = [[NSMutableDictionary alloc] init];
  104. self.outstandingPuts = [[NSMutableDictionary alloc] init];
  105. self.onDisconnectQueue = [[NSMutableArray alloc] init];
  106. self.putCounter = [[FAtomicNumber alloc] init];
  107. self.requestNumber = [[FAtomicNumber alloc] init];
  108. self.requestCBHash = [[NSMutableDictionary alloc] init];
  109. self.unackedListensCount = 0;
  110. self.putsToAck = [NSMutableArray array];
  111. connectionState = ConnectionStateDisconnected;
  112. firstConnection = YES;
  113. reconnectDelay = kPersistentConnReconnectMinDelay;
  114. self->_retryHelper = [[FIRRetryHelper alloc] initWithDispatchQueue:dispatchQueue
  115. minRetryDelayAfterFailure:kPersistentConnReconnectMinDelay
  116. maxRetryDelay:kPersistentConnReconnectMaxDelay
  117. retryExponent:kPersistentConnReconnectMultiplier
  118. jitterFactor:0.7];
  119. [self setupNotifications];
  120. // Make sure we don't actually connect until open is called
  121. [self interruptForReason:kFInterruptReasonWaitingForOpen];
  122. }
  123. // nb: The reason establishConnection isn't called here like the JS version is because
  124. // callers need to set the delegate first. The ctor can be modified to accept the delegate
  125. // but that deviates from normal ios conventions. After the delegate has been set, the caller
  126. // is responsible for calling establishConnection:
  127. return self;
  128. }
  129. - (void) dealloc {
  130. if (reachability) {
  131. // Unschedule the notifications
  132. SCNetworkReachabilitySetDispatchQueue(reachability, NULL);
  133. CFRelease(reachability);
  134. }
  135. }
  136. #pragma mark -
  137. #pragma mark Public methods
  138. - (void) open {
  139. [self resumeForReason:kFInterruptReasonWaitingForOpen];
  140. }
  141. /**
  142. * Note that the listens dictionary has a type of Map[String (pathString), Map[FQueryParams, FOutstandingQuery]]
  143. *
  144. * This means, for each path we care about, there are sets of queryParams that correspond to an FOutstandingQuery object.
  145. * There can be multiple sets at a path since we overlap listens for a short time while adding or removing a query from a
  146. * location in the tree.
  147. */
  148. - (void) listen:(FQuerySpec *)query
  149. tagId:(NSNumber *)tagId
  150. hash:(id<FSyncTreeHash>)hash
  151. onComplete:(fbt_void_nsstring)onComplete {
  152. FFLog(@"I-RDB034001", @"Listen called for %@", query);
  153. NSAssert(self.listens[query] == nil, @"listen() called twice for the same query");
  154. NSAssert(query.isDefault || !query.loadsAllData, @"listen called for non-default but complete query");
  155. FOutstandingQuery* outstanding = [[FOutstandingQuery alloc] init];
  156. outstanding.query = query;
  157. outstanding.tagId = tagId;
  158. outstanding.syncTreeHash = hash;
  159. outstanding.onComplete = onComplete;
  160. [self.listens setObject:outstanding forKey:query];
  161. if ([self connected]) {
  162. [self sendListen:outstanding];
  163. }
  164. }
  165. - (void) putData:(id)data forPath:(NSString *)pathString withHash:(NSString *)hash withCallback:(fbt_void_nsstring_nsstring)onComplete {
  166. [self putInternal:data forAction:kFWPRequestActionPut forPath:pathString withHash:hash withCallback:onComplete];
  167. }
  168. - (void) mergeData:(id)data forPath:(NSString *)pathString withCallback:(fbt_void_nsstring_nsstring)onComplete {
  169. [self putInternal:data forAction:kFWPRequestActionMerge forPath:pathString withHash:nil withCallback:onComplete];
  170. }
  171. - (void) onDisconnectPutData:(id)data forPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback {
  172. if ([self canSendWrites]) {
  173. [self sendOnDisconnectAction:kFWPRequestActionDisconnectPut forPath:[path description] withData:data andCallback:callback];
  174. } else {
  175. FTupleOnDisconnect* tuple = [[FTupleOnDisconnect alloc] init];
  176. tuple.pathString = [path description];
  177. tuple.action = kFWPRequestActionDisconnectPut;
  178. tuple.data = data;
  179. tuple.onComplete = callback;
  180. [self.onDisconnectQueue addObject:tuple];
  181. }
  182. }
  183. - (void) onDisconnectMergeData:(id)data forPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback {
  184. if ([self canSendWrites]) {
  185. [self sendOnDisconnectAction:kFWPRequestActionDisconnectMerge forPath:[path description] withData:data andCallback:callback];
  186. } else {
  187. FTupleOnDisconnect* tuple = [[FTupleOnDisconnect alloc] init];
  188. tuple.pathString = [path description];
  189. tuple.action = kFWPRequestActionDisconnectMerge;
  190. tuple.data = data;
  191. tuple.onComplete = callback;
  192. [self.onDisconnectQueue addObject:tuple];
  193. }
  194. }
  195. - (void) onDisconnectCancelPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback {
  196. if ([self canSendWrites]) {
  197. [self sendOnDisconnectAction:kFWPRequestActionDisconnectCancel forPath:[path description] withData:[NSNull null] andCallback:callback];
  198. } else {
  199. FTupleOnDisconnect* tuple = [[FTupleOnDisconnect alloc] init];
  200. tuple.pathString = [path description];
  201. tuple.action = kFWPRequestActionDisconnectCancel;
  202. tuple.data = [NSNull null];
  203. tuple.onComplete = callback;
  204. [self.onDisconnectQueue addObject:tuple];
  205. }
  206. }
  207. - (void) unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId {
  208. FPath *path = query.path;
  209. FFLog(@"I-RDB034002", @"Unlistening for %@", query);
  210. NSArray *outstanding = [self removeListen:query];
  211. if (outstanding.count > 0 && [self connected]) {
  212. [self sendUnlisten:path queryParams:query.params tagId:tagId];
  213. }
  214. }
  215. - (void) refreshAuthToken:(NSString *)token {
  216. self.authToken = token;
  217. if ([self connected]) {
  218. if (token != nil) {
  219. [self sendAuthAndRestoreStateAfterComplete:NO];
  220. } else {
  221. [self sendUnauth];
  222. }
  223. }
  224. }
  225. #pragma mark -
  226. #pragma mark Connection status
  227. - (BOOL)connected {
  228. return self->connectionState == ConnectionStateAuthenticating || self->connectionState == ConnectionStateConnected;
  229. }
  230. - (BOOL)canSendWrites {
  231. return self->connectionState == ConnectionStateConnected;
  232. }
  233. #pragma mark -
  234. #pragma mark FConnection delegate methods
  235. - (void)onReady:(FConnection *)fconnection atTime:(NSNumber *)timestamp sessionID:(NSString *)sessionID {
  236. FFLog(@"I-RDB034003", @"On ready");
  237. lastConnectionEstablishedTime = [[NSDate date] timeIntervalSince1970];
  238. [self handleTimestamp:timestamp];
  239. if (firstConnection) {
  240. [self sendConnectStats];
  241. }
  242. [self restoreAuth];
  243. firstConnection = NO;
  244. self.lastSessionID = sessionID;
  245. dispatch_async(self.dispatchQueue, ^{
  246. [self.delegate onConnect:self];
  247. });
  248. }
  249. - (void)onDataMessage:(FConnection *)fconnection withMessage:(NSDictionary *)message {
  250. if (message[kFWPRequestNumber] != nil) {
  251. // this is a response to a request we sent
  252. NSNumber* rn = [NSNumber numberWithInt:[[message objectForKey:kFWPRequestNumber] intValue]];
  253. if ([self.requestCBHash objectForKey:rn]) {
  254. void (^callback)(NSDictionary*) = [self.requestCBHash objectForKey:rn];
  255. [self.requestCBHash removeObjectForKey:rn];
  256. if (callback) {
  257. //dispatch_async(self.dispatchQueue, ^{
  258. callback([message objectForKey:kFWPResponseForRNData]);
  259. //});
  260. }
  261. }
  262. } else if (message[kFWPRequestError] != nil) {
  263. NSString* error = [message objectForKey:kFWPRequestError];
  264. @throw [[NSException alloc] initWithName:@"FirebaseDatabaseServerError" reason:error userInfo:nil];
  265. } else if (message[kFWPAsyncServerAction] != nil) {
  266. // this is a server push of some sort
  267. NSString* action = [message objectForKey:kFWPAsyncServerAction];
  268. NSDictionary* body = [message objectForKey:kFWPAsyncServerPayloadBody];
  269. [self onDataPushWithAction:action andBody:body];
  270. }
  271. }
  272. - (void)onDisconnect:(FConnection *)fconnection withReason:(FDisconnectReason)reason {
  273. FFLog(@"I-RDB034004", @"Got on disconnect due to %s", (reason == DISCONNECT_REASON_SERVER_RESET) ? "server_reset" : "other");
  274. connectionState = ConnectionStateDisconnected;
  275. // Drop the realtime connection
  276. self.realtime = nil;
  277. [self cancelSentTransactions];
  278. [self.requestCBHash removeAllObjects];
  279. self.unackedListensCount = 0;
  280. if ([self shouldReconnect]) {
  281. NSTimeInterval timeSinceLastConnectSucceeded = [[NSDate date] timeIntervalSince1970] - lastConnectionEstablishedTime;
  282. BOOL lastConnectionWasSuccessful;
  283. if (lastConnectionEstablishedTime > 0) {
  284. lastConnectionWasSuccessful = timeSinceLastConnectSucceeded > kPersistentConnSuccessfulConnectionEstablishedDelay;
  285. } else {
  286. lastConnectionWasSuccessful = NO;
  287. }
  288. if (reason == DISCONNECT_REASON_SERVER_RESET || lastConnectionWasSuccessful) {
  289. [self.retryHelper signalSuccess];
  290. }
  291. [self tryScheduleReconnect];
  292. }
  293. lastConnectionEstablishedTime = 0;
  294. [self.delegate onDisconnect:self];
  295. }
  296. - (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason {
  297. FFWarn(@"I-RDB034005", @"Firebase Database connection was forcefully killed by the server. Will not attempt reconnect. Reason: %@", reason);
  298. [self interruptForReason:kFInterruptReasonServerKill];
  299. }
  300. #pragma mark -
  301. #pragma mark Connection handling methods
  302. - (void) interruptForReason:(NSString *)reason {
  303. FFLog(@"I-RDB034006", @"Connection interrupted for: %@", reason);
  304. [self.interruptReasons addObject:reason];
  305. if (self.realtime) {
  306. // Will call onDisconnect and set the connection state to Disconnected
  307. [self.realtime close];
  308. self.realtime = nil;
  309. } else {
  310. [self.retryHelper cancel];
  311. self->connectionState = ConnectionStateDisconnected;
  312. }
  313. // Reset timeouts
  314. [self.retryHelper signalSuccess];
  315. }
  316. - (void) resumeForReason:(NSString *)reason {
  317. FFLog(@"I-RDB034007", @"Connection no longer interrupted for: %@", reason);
  318. [self.interruptReasons removeObject:reason];
  319. if ([self shouldReconnect] && connectionState == ConnectionStateDisconnected) {
  320. [self tryScheduleReconnect];
  321. }
  322. }
  323. - (BOOL) shouldReconnect {
  324. return self.interruptReasons.count == 0;
  325. }
  326. - (BOOL) isInterruptedForReason:(NSString *)reason {
  327. return [self.interruptReasons containsObject:reason];
  328. }
  329. #pragma mark -
  330. #pragma mark Private methods
  331. - (void) tryScheduleReconnect {
  332. if ([self shouldReconnect]) {
  333. NSAssert(self->connectionState == ConnectionStateDisconnected,
  334. @"Not in disconnected state: %d", self->connectionState);
  335. BOOL forceRefresh = self.forceAuthTokenRefresh;
  336. self.forceAuthTokenRefresh = NO;
  337. FFLog(@"I-RDB034008", @"Scheduling connection attempt");
  338. [self.retryHelper retry:^{
  339. FFLog(@"I-RDB034009", @"Trying to fetch auth token");
  340. NSAssert(self->connectionState == ConnectionStateDisconnected,
  341. @"Not in disconnected state: %d", self->connectionState);
  342. self->connectionState = ConnectionStateGettingToken;
  343. self.currentFetchTokenAttempt++;
  344. NSUInteger thisFetchTokenAttempt = self.currentFetchTokenAttempt;
  345. [self.authTokenProvider fetchTokenForcingRefresh:forceRefresh withCallback:^(NSString *token, NSError *error) {
  346. if (thisFetchTokenAttempt == self.currentFetchTokenAttempt) {
  347. if (error != nil) {
  348. self->connectionState = ConnectionStateDisconnected;
  349. FFLog(@"I-RDB034010", @"Error fetching token: %@", error);
  350. [self tryScheduleReconnect];
  351. } else {
  352. // Someone could have interrupted us while fetching the token,
  353. // marking the connection as Disconnected
  354. if (self->connectionState == ConnectionStateGettingToken) {
  355. FFLog(@"I-RDB034011", @"Successfully fetched token, opening connection");
  356. [self openNetworkConnectionWithToken:token];
  357. } else {
  358. NSAssert(self->connectionState == ConnectionStateDisconnected,
  359. @"Expected connection state disconnected, but got %d", self->connectionState);
  360. FFLog(@"I-RDB034012", @"Not opening connection after token refresh, because connection was set to disconnected.");
  361. }
  362. }
  363. } else {
  364. FFLog(@"I-RDB034013", @"Ignoring fetch token result, because this was not the latest attempt.");
  365. }
  366. }];
  367. }];
  368. }
  369. }
  370. - (void) openNetworkConnectionWithToken:(NSString *)token {
  371. NSAssert(self->connectionState == ConnectionStateGettingToken,
  372. @"Trying to open network connection while in wrong state: %d", self->connectionState);
  373. self.authToken = token;
  374. self->connectionState = ConnectionStateConnecting;
  375. self.realtime = [[FConnection alloc] initWith:self.repoInfo
  376. andDispatchQueue:self.dispatchQueue
  377. lastSessionID:self.lastSessionID];
  378. self.realtime.delegate = self;
  379. [self.realtime open];
  380. }
  381. static void reachabilityCallback(SCNetworkReachabilityRef ref, SCNetworkReachabilityFlags flags, void* info) {
  382. if (flags & kSCNetworkReachabilityFlagsReachable) {
  383. FFLog(@"I-RDB034014", @"Network became reachable. Trigger a connection attempt");
  384. FPersistentConnection* self = (__bridge FPersistentConnection *)info;
  385. // Reset reconnect delay
  386. [self.retryHelper signalSuccess];
  387. if (self->connectionState == ConnectionStateDisconnected) {
  388. [self tryScheduleReconnect];
  389. }
  390. } else {
  391. FFLog(@"I-RDB034015", @"Network is not reachable");
  392. }
  393. }
  394. - (void) enteringForeground {
  395. dispatch_async(self.dispatchQueue, ^{
  396. // Reset reconnect delay
  397. [self.retryHelper signalSuccess];
  398. if (self->connectionState == ConnectionStateDisconnected) {
  399. [self tryScheduleReconnect];
  400. }
  401. });
  402. }
  403. - (void) setupNotifications {
  404. NSString * const* foregroundConstant = (NSString * const *) dlsym(RTLD_DEFAULT, "UIApplicationWillEnterForegroundNotification");
  405. if (foregroundConstant) {
  406. [[NSNotificationCenter defaultCenter] addObserver:self
  407. selector:@selector(enteringForeground)
  408. name:*foregroundConstant
  409. object:nil];
  410. }
  411. // An empty address is interpreted a generic internet access
  412. struct sockaddr_in zeroAddress;
  413. bzero(&zeroAddress, sizeof(zeroAddress));
  414. zeroAddress.sin_len = sizeof(zeroAddress);
  415. zeroAddress.sin_family = AF_INET;
  416. reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress);
  417. SCNetworkReachabilityContext ctx = {0, (__bridge void *)(self), NULL, NULL, NULL};
  418. if (SCNetworkReachabilitySetCallback(reachability, reachabilityCallback, &ctx)) {
  419. SCNetworkReachabilitySetDispatchQueue(reachability, self.dispatchQueue);
  420. } else {
  421. FFLog(@"I-RDB034016", @"Failed to set up network reachability monitoring");
  422. CFRelease(reachability);
  423. reachability = NULL;
  424. }
  425. }
  426. - (void) sendAuthAndRestoreStateAfterComplete:(BOOL)restoreStateAfterComplete {
  427. NSAssert([self connected], @"Must be connected to send auth");
  428. NSAssert(self.authToken != nil, @"Can't send auth if there is no credential");
  429. NSDictionary* requestData = @{kFWPRequestCredential: self.authToken};
  430. [self sendAction:kFWPRequestActionAuth body:requestData sensitive:YES callback:^(NSDictionary *data) {
  431. self->connectionState = ConnectionStateConnected;
  432. NSString* status = [data objectForKey:kFWPResponseForActionStatus];
  433. id responseData = [data objectForKey:kFWPResponseForActionData];
  434. if (responseData == nil) {
  435. responseData = @"error";
  436. }
  437. BOOL statusOk = [status isEqualToString:kFWPResponseForActionStatusOk];
  438. if (statusOk) {
  439. if (restoreStateAfterComplete) {
  440. [self restoreState];
  441. }
  442. } else {
  443. self.authToken = nil;
  444. self.forceAuthTokenRefresh = YES;
  445. if ([status isEqualToString:@"expired_token"]) {
  446. FFLog(@"I-RDB034017", @"Authentication failed: %@ (%@)", status, responseData);
  447. } else {
  448. FFWarn(@"I-RDB034018", @"Authentication failed: %@ (%@)", status, responseData);
  449. }
  450. [self.realtime close];
  451. }
  452. }];
  453. }
  454. - (void) sendUnauth {
  455. [self sendAction:kFWPRequestActionUnauth body:@{} sensitive:NO callback:nil];
  456. }
  457. - (void) onAuthRevokedWithStatus:(NSString *)status andReason:(NSString *)reason {
  458. // This might be for an earlier token than we just recently sent. But since we need to close the connection anyways,
  459. // we can set it to null here and we will refresh the token later on reconnect
  460. if ([status isEqualToString:@"expired_token"]) {
  461. FFLog(@"I-RDB034019", @"Auth token revoked: %@ (%@)", status, reason);
  462. } else {
  463. FFWarn(@"I-RDB034020", @"Auth token revoked: %@ (%@)", status, reason);
  464. }
  465. self.authToken = nil;
  466. self.forceAuthTokenRefresh = YES;
  467. // Try reconnecting on auth revocation
  468. [self.realtime close];
  469. }
  470. - (void) onListenRevoked:(FPath *)path {
  471. NSArray *queries = [self removeAllListensAtPath:path];
  472. for (FOutstandingQuery* query in queries) {
  473. query.onComplete(@"permission_denied");
  474. }
  475. }
  476. - (void) sendOnDisconnectAction:(NSString *)action forPath:(NSString *)pathString withData:(id)data andCallback:(fbt_void_nsstring_nsstring)callback {
  477. NSDictionary* request = @{kFWPRequestPath: pathString, kFWPRequestData: data};
  478. FFLog(@"I-RDB034021", @"onDisconnect %@: %@", action, request);
  479. [self sendAction:action
  480. body:request
  481. sensitive:NO
  482. callback:^(NSDictionary *data) {
  483. NSString* status = [data objectForKey:kFWPResponseForActionStatus];
  484. NSString* errorReason = [data objectForKey:kFWPResponseForActionData];
  485. callback(status, errorReason);
  486. }];
  487. }
  488. - (void) sendPut:(NSNumber *) index {
  489. NSAssert([self canSendWrites], @"sendPut called when not able to send writes");
  490. FOutstandingPut* put = self.outstandingPuts[index];
  491. assert(put != nil);
  492. fbt_void_nsstring_nsstring onComplete = put.onCompleteBlock;
  493. // Do not async this block; copying the block insinde sendAction: doesn't happen in time (or something) so coredumps
  494. put.sent = YES;
  495. [self sendAction:put.action
  496. body:put.request
  497. sensitive:NO
  498. callback:^(NSDictionary* data) {
  499. FOutstandingPut *currentPut = self.outstandingPuts[index];
  500. if (currentPut == put) {
  501. [self.outstandingPuts removeObjectForKey:index];
  502. if (onComplete != nil) {
  503. NSString *status = [data objectForKey:kFWPResponseForActionStatus];
  504. NSString *errorReason = [data objectForKey:kFWPResponseForActionData];
  505. if (self.unackedListensCount == 0) {
  506. onComplete(status, errorReason);
  507. } else {
  508. FTupleCallbackStatus *putToAck = [[FTupleCallbackStatus alloc] init];
  509. putToAck.block = onComplete;
  510. putToAck.status = status;
  511. putToAck.errorReason = errorReason;
  512. [self.putsToAck addObject:putToAck];
  513. }
  514. }
  515. } else {
  516. FFLog(@"I-RDB034022", @"Ignoring on complete for put %@ because it was already removed", index);
  517. }
  518. }];
  519. }
  520. - (void) sendUnlisten:(FPath *)path queryParams:(FQueryParams *)queryParams tagId:(NSNumber *)tagId {
  521. FFLog(@"I-RDB034023", @"Unlisten on %@ for %@", path, queryParams);
  522. NSMutableDictionary* request = [NSMutableDictionary dictionaryWithObjectsAndKeys:[path toString], kFWPRequestPath, nil];
  523. if (tagId) {
  524. [request setObject:queryParams.wireProtocolParams forKey:kFWPRequestQueries];
  525. [request setObject:tagId forKey:kFWPRequestTag];
  526. }
  527. [self sendAction:kFWPRequestActionTaggedUnlisten
  528. body:request
  529. sensitive:NO
  530. callback:nil];
  531. }
  532. - (void) putInternal:(id)data forAction:(NSString *)action forPath:(NSString *)pathString withHash:(NSString *)hash withCallback:(fbt_void_nsstring_nsstring)onComplete {
  533. NSMutableDictionary *request = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  534. pathString, kFWPRequestPath,
  535. data, kFWPRequestData, nil];
  536. if(hash) {
  537. [request setObject:hash forKey:kFWPRequestHash];
  538. }
  539. FOutstandingPut *put = [[FOutstandingPut alloc] init];
  540. put.action = action;
  541. put.request = request;
  542. put.onCompleteBlock = onComplete;
  543. put.sent = NO;
  544. NSNumber* index = [self.putCounter getAndIncrement];
  545. self.outstandingPuts[index] = put;
  546. if ([self canSendWrites]) {
  547. FFLog(@"I-RDB034024", @"Was connected, and added as index: %@", index);
  548. [self sendPut:index];
  549. }
  550. else {
  551. FFLog(@"I-RDB034025", @"Wasn't connected or writes paused, so added to outstanding puts only. Path: %@", pathString);
  552. }
  553. }
  554. - (void) sendListen:(FOutstandingQuery *)listenSpec {
  555. FQuerySpec *query = listenSpec.query;
  556. FFLog(@"I-RDB034026", @"Listen for %@", query);
  557. NSMutableDictionary *request = [NSMutableDictionary dictionaryWithObject:[query.path toString] forKey:kFWPRequestPath];
  558. // Only bother to send query if it's non-default
  559. if (listenSpec.tagId != nil) {
  560. [request setObject:[query.params wireProtocolParams] forKey:kFWPRequestQueries];
  561. [request setObject:listenSpec.tagId forKey:kFWPRequestTag];
  562. }
  563. [request setObject:[listenSpec.syncTreeHash simpleHash] forKey:kFWPRequestHash];
  564. if ([listenSpec.syncTreeHash includeCompoundHash]) {
  565. FCompoundHash *compoundHash = [listenSpec.syncTreeHash compoundHash];
  566. NSMutableArray *posts = [NSMutableArray array];
  567. for (FPath *path in compoundHash.posts) {
  568. [posts addObject:path.wireFormat];
  569. }
  570. request[kFWPRequestCompoundHash] = @{ kFWPRequestCompoundHashHashes: compoundHash.hashes,
  571. kFWPRequestCompoundHashPaths: posts };
  572. }
  573. fbt_void_nsdictionary onResponse = ^(NSDictionary *response) {
  574. FFLog(@"I-RDB034027", @"Listen response %@", response);
  575. // warn in any case, even if the listener was removed
  576. [self warnOnListenWarningsForQuery:query payload:response[kFWPResponseForActionData]];
  577. FOutstandingQuery *currentListenSpec = self.listens[query];
  578. // only trigger actions if the listen hasn't been removed (and maybe readded)
  579. if (currentListenSpec == listenSpec) {
  580. NSString *status = [response objectForKey:kFWPRequestStatus];
  581. if (![status isEqualToString:@"ok"]) {
  582. [self removeListen:query];
  583. }
  584. if (listenSpec.onComplete) {
  585. listenSpec.onComplete(status);
  586. }
  587. }
  588. self.unackedListensCount--;
  589. NSAssert(self.unackedListensCount >= 0, @"unackedListensCount decremented to be negative.");
  590. if (self.unackedListensCount == 0) {
  591. [self ackPuts];
  592. }
  593. };
  594. [self sendAction:kFWPRequestActionTaggedListen
  595. body:request
  596. sensitive:NO
  597. callback:onResponse];
  598. self.unackedListensCount++;
  599. }
  600. - (void) warnOnListenWarningsForQuery:(FQuerySpec *)query payload:(id)payload {
  601. if (payload != nil && [payload isKindOfClass:[NSDictionary class]]) {
  602. NSDictionary *payloadDict = payload;
  603. id warnings = payloadDict[kFWPResponseDataWarnings];
  604. if (warnings != nil && [warnings isKindOfClass:[NSArray class]]) {
  605. NSArray *warningsArr = warnings;
  606. if ([warningsArr containsObject:@"no_index"]) {
  607. NSString *indexSpec = [NSString stringWithFormat:@"\".indexOn\": \"%@\"", [query.params.index queryDefinition]];
  608. NSString *indexPath = [query.path description];
  609. FFWarn(@"I-RDB034028", @"Using an unspecified index. Consider adding %@ at %@ to your security rules for better performance", indexSpec, indexPath);
  610. }
  611. }
  612. }
  613. }
  614. - (int) getNextRequestNumber {
  615. return [[self.requestNumber getAndIncrement] intValue];
  616. }
  617. - (void)sendAction:(NSString *)action
  618. body:(NSDictionary *)message
  619. sensitive:(BOOL)sensitive
  620. callback:(void (^)(NSDictionary* data))onMessage {
  621. // Hold onto the onMessage callback for this request before firing it off
  622. NSNumber* rn = [NSNumber numberWithInt:[self getNextRequestNumber]];
  623. NSDictionary* msg = [NSDictionary dictionaryWithObjectsAndKeys:
  624. rn, kFWPRequestNumber,
  625. action, kFWPRequestAction,
  626. message, kFWPRequestPayloadBody,
  627. nil];
  628. [self.realtime sendRequest:msg sensitive:sensitive];
  629. if (onMessage) {
  630. // Debug message without a callback; bump the rn, but don't hold onto the cb
  631. [self.requestCBHash setObject:[onMessage copy] forKey:rn];
  632. }
  633. }
  634. - (void) cancelSentTransactions {
  635. NSMutableArray* toPrune = [[NSMutableArray alloc] init];
  636. for (NSNumber* index in self.outstandingPuts) {
  637. FOutstandingPut* put = self.outstandingPuts[index];
  638. if (put.request[kFWPRequestHash] && put.sent) {
  639. // This is a sent transaction put
  640. put.onCompleteBlock(kFTransactionDisconnect, @"Client was disconnected while running a transaction");
  641. [toPrune addObject:index];
  642. }
  643. }
  644. for (NSNumber* index in toPrune) {
  645. [self.outstandingPuts removeObjectForKey:index];
  646. }
  647. }
  648. - (void) onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body {
  649. FFLog(@"I-RDB034029", @"handleServerMessage: %@, %@", action, body);
  650. id<FPersistentConnectionDelegate> delegate = self.delegate;
  651. if ([action isEqualToString:kFWPAsyncServerDataUpdate] || [action isEqualToString:kFWPAsyncServerDataMerge]) {
  652. BOOL isMerge = [action isEqualToString:kFWPAsyncServerDataMerge];
  653. if ([body objectForKey:kFWPAsyncServerDataUpdateBodyPath] && [body objectForKey:kFWPAsyncServerDataUpdateBodyData]) {
  654. NSString* path = [body objectForKey:kFWPAsyncServerDataUpdateBodyPath];
  655. id payloadData = [body objectForKey:kFWPAsyncServerDataUpdateBodyData];
  656. if (isMerge && [payloadData isKindOfClass:[NSDictionary class]] && [payloadData count] == 0) {
  657. // ignore empty merge
  658. } else {
  659. [delegate onDataUpdate:self forPath:path message:payloadData isMerge:isMerge tagId:[body objectForKey:kFWPAsyncServerDataUpdateBodyTag]];
  660. }
  661. }
  662. else {
  663. FFLog(@"I-RDB034030", @"Malformed data response from server missing path or data: %@", body);
  664. }
  665. } else if ([action isEqualToString:kFWPAsyncServerDataRangeMerge]) {
  666. NSString *path = body[kFWPAsyncServerDataUpdateBodyPath];
  667. NSArray *ranges = body[kFWPAsyncServerDataUpdateBodyData];
  668. NSNumber *tag = body[kFWPAsyncServerDataUpdateBodyTag];
  669. NSMutableArray *rangeMerges = [NSMutableArray array];
  670. for (NSDictionary *range in ranges) {
  671. NSString *startString = range[kFWPAsyncServerDataUpdateStartPath];
  672. NSString *endString = range[kFWPAsyncServerDataUpdateEndPath];
  673. id updateData = range[kFWPAsyncServerDataUpdateRangeMerge];
  674. id<FNode> updates = [FSnapshotUtilities nodeFrom:updateData];
  675. FPath *start = (startString != nil) ? [[FPath alloc] initWith:startString] : nil;
  676. FPath *end = (endString != nil) ? [[FPath alloc] initWith:endString] : nil;
  677. FRangeMerge *merge = [[FRangeMerge alloc] initWithStart:start end:end updates:updates];
  678. [rangeMerges addObject:merge];
  679. }
  680. [delegate onRangeMerge:rangeMerges forPath:path tagId:tag];
  681. } else if ([action isEqualToString:kFWPAsyncServerAuthRevoked]) {
  682. NSString* status = [body objectForKey:kFWPResponseForActionStatus];
  683. NSString* reason = [body objectForKey:kFWPResponseForActionData];
  684. [self onAuthRevokedWithStatus:status andReason:reason];
  685. } else if ([action isEqualToString:kFWPASyncServerListenCancelled]) {
  686. NSString* pathString = [body objectForKey:kFWPAsyncServerDataUpdateBodyPath];
  687. [self onListenRevoked:[[FPath alloc] initWith:pathString]];
  688. } else if ([action isEqualToString:kFWPAsyncServerSecurityDebug]) {
  689. NSString* msg = [body objectForKey:@"msg"];
  690. if (msg != nil) {
  691. NSArray *msgs = [msg componentsSeparatedByString:@"\n"];
  692. for (NSString* m in msgs) {
  693. FFWarn(@"I-RDB034031", @"%@", m);
  694. }
  695. }
  696. } else {
  697. // TODO: revoke listens, auth, security debug
  698. FFLog(@"I-RDB034032", @"Unsupported action from server: %@", action);
  699. }
  700. }
  701. - (void) restoreAuth {
  702. FFLog(@"I-RDB034033", @"Calling restore state");
  703. NSAssert(self->connectionState == ConnectionStateConnecting,
  704. @"Wanted to restore auth, but was in wrong state: %d", self->connectionState);
  705. if (self.authToken == nil) {
  706. FFLog(@"I-RDB034034", @"Not restoring auth because token is nil");
  707. self->connectionState = ConnectionStateConnected;
  708. [self restoreState];
  709. } else {
  710. FFLog(@"I-RDB034035", @"Restoring auth");
  711. self->connectionState = ConnectionStateAuthenticating;
  712. [self sendAuthAndRestoreStateAfterComplete:YES];
  713. }
  714. }
  715. - (void) restoreState {
  716. NSAssert(self->connectionState == ConnectionStateConnected,
  717. @"Should be connected if we're restoring state, but we are: %d", self->connectionState);
  718. [self.listens enumerateKeysAndObjectsUsingBlock:^(FQuerySpec *query, FOutstandingQuery *outstandingListen, BOOL *stop) {
  719. FFLog(@"I-RDB034036", @"Restoring listen for %@", query);
  720. [self sendListen:outstandingListen];
  721. }];
  722. NSArray* keys = [[self.outstandingPuts allKeys] sortedArrayUsingSelector:@selector(compare:)];
  723. for(int i = 0; i < [keys count]; i++) {
  724. if([self.outstandingPuts objectForKey:[keys objectAtIndex:i]] != nil) {
  725. FFLog(@"I-RDB034037", @"Restoring put: %d", i);
  726. [self sendPut:[keys objectAtIndex:i]];
  727. }
  728. else {
  729. FFLog(@"I-RDB034038", @"Restoring put: skipped nil: %d", i);
  730. }
  731. }
  732. for (FTupleOnDisconnect* tuple in self.onDisconnectQueue) {
  733. [self sendOnDisconnectAction:tuple.action forPath:tuple.pathString withData:tuple.data andCallback:tuple.onComplete];
  734. }
  735. [self.onDisconnectQueue removeAllObjects];
  736. }
  737. - (NSArray *) removeListen:(FQuerySpec *)query {
  738. NSAssert(query.isDefault || !query.loadsAllData, @"removeListen called for non-default but complete query");
  739. FOutstandingQuery* outstanding = self.listens[query];
  740. if (!outstanding) {
  741. FFLog(@"I-RDB034039", @"Trying to remove listener for query %@ but no listener exists", query);
  742. return @[];
  743. } else {
  744. [self.listens removeObjectForKey:query];
  745. return @[outstanding];
  746. }
  747. }
  748. - (NSArray *) removeAllListensAtPath:(FPath *)path {
  749. FFLog(@"I-RDB034040", @"Removing all listens at path %@", path);
  750. NSMutableArray *removed = [NSMutableArray array];
  751. NSMutableArray *toRemove = [NSMutableArray array];
  752. [self.listens enumerateKeysAndObjectsUsingBlock:^(FQuerySpec *spec, FOutstandingQuery *outstanding, BOOL *stop) {
  753. if ([spec.path isEqual:path]) {
  754. [removed addObject:outstanding];
  755. [toRemove addObject:spec];
  756. }
  757. }];
  758. [self.listens removeObjectsForKeys:toRemove];
  759. return removed;
  760. }
  761. - (void) purgeOutstandingWrites {
  762. // We might have unacked puts in our queue that we need to ack now before we send out any cancels...
  763. [self ackPuts];
  764. // Cancel in order
  765. NSArray* keys = [[self.outstandingPuts allKeys] sortedArrayUsingSelector:@selector(compare:)];
  766. for (NSNumber *key in keys) {
  767. FOutstandingPut *put = self.outstandingPuts[key];
  768. if (put.onCompleteBlock != nil) {
  769. put.onCompleteBlock(kFErrorWriteCanceled, nil);
  770. }
  771. }
  772. for (FTupleOnDisconnect *onDisconnect in self.onDisconnectQueue) {
  773. if (onDisconnect.onComplete != nil) {
  774. onDisconnect.onComplete(kFErrorWriteCanceled, nil);
  775. }
  776. }
  777. [self.outstandingPuts removeAllObjects];
  778. [self.onDisconnectQueue removeAllObjects];
  779. }
  780. - (void) ackPuts {
  781. for (FTupleCallbackStatus *put in self.putsToAck) {
  782. put.block(put.status, put.errorReason);
  783. }
  784. [self.putsToAck removeAllObjects];
  785. }
  786. - (void) handleTimestamp:(NSNumber *)timestamp {
  787. FFLog(@"I-RDB034041", @"Handling timestamp: %@", timestamp);
  788. double timestampDeltaMs = [timestamp doubleValue] - ([[NSDate date] timeIntervalSince1970] * 1000);
  789. [self.delegate onServerInfoUpdate:self updates:@{kDotInfoServerTimeOffset: [NSNumber numberWithDouble:timestampDeltaMs]}];
  790. }
  791. - (void) sendStats:(NSDictionary *)stats {
  792. if ([stats count] > 0) {
  793. NSDictionary *request = @{ kFWPRequestCounters: stats };
  794. [self sendAction:kFWPRequestActionStats body:request sensitive:NO callback:^(NSDictionary *data) {
  795. NSString* status = [data objectForKey:kFWPResponseForActionStatus];
  796. NSString* errorReason = [data objectForKey:kFWPResponseForActionData];
  797. BOOL statusOk = [status isEqualToString:kFWPResponseForActionStatusOk];
  798. if (!statusOk) {
  799. FFLog(@"I-RDB034042", @"Failed to send stats: %@", errorReason);
  800. }
  801. }];
  802. } else {
  803. FFLog(@"I-RDB034043", @"Not sending stats because stats are empty");
  804. }
  805. }
  806. - (void) sendConnectStats {
  807. NSMutableDictionary *stats = [NSMutableDictionary dictionary];
  808. #if TARGET_OS_IOS
  809. if (self.config.persistenceEnabled) {
  810. stats[@"persistence.ios.enabled"] = @1;
  811. }
  812. #elif TARGET_OS_OSX
  813. if (self.config.persistenceEnabled) {
  814. stats[@"persistence.osx.enabled"] = @1;
  815. }
  816. #endif
  817. NSString *sdkVersion = [[FIRDatabase sdkVersion] stringByReplacingOccurrencesOfString:@"." withString:@"-"];
  818. NSString *sdkStatName = [NSString stringWithFormat:@"sdk.objc.%@", sdkVersion];
  819. stats[sdkStatName] = @1;
  820. FFLog(@"I-RDB034044", @"Sending first connection stats");
  821. [self sendStats:stats];
  822. }
  823. - (NSDictionary *) dumpListens {
  824. return self.listens;
  825. }
  826. @end