FPersistentConnection.m 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
  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 "FirebaseCore/Extension/FirebaseCoreInternal.h"
  18. #import "FirebaseDatabase/Sources/Api/FIRDatabaseConfig.h"
  19. #import "FirebaseDatabase/Sources/Constants/FConstants.h"
  20. #import "FirebaseDatabase/Sources/Core/FCompoundHash.h"
  21. #import "FirebaseDatabase/Sources/Core/FPersistentConnection.h"
  22. #import "FirebaseDatabase/Sources/Core/FQueryParams.h"
  23. #import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
  24. #import "FirebaseDatabase/Sources/Core/FRangeMerge.h"
  25. #import "FirebaseDatabase/Sources/Core/FSyncTree.h"
  26. #import "FirebaseDatabase/Sources/Core/Utilities/FIRRetryHelper.h"
  27. #import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
  28. #import "FirebaseDatabase/Sources/FIndex.h"
  29. #import "FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.h"
  30. #import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseReference.h"
  31. #import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
  32. #import "FirebaseDatabase/Sources/Utilities/FAtomicNumber.h"
  33. #import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  34. #import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleCallbackStatus.h"
  35. #import "FirebaseDatabase/Sources/Utilities/Tuples/FTupleOnDisconnect.h"
  36. #if !TARGET_OS_WATCH
  37. #import <SystemConfiguration/SystemConfiguration.h>
  38. #endif // !TARGET_OS_WATCH
  39. #import <dlfcn.h>
  40. #import <netinet/in.h>
  41. @interface FOutstandingQuery : NSObject
  42. @property(nonatomic, strong) FQuerySpec *query;
  43. @property(nonatomic, strong) NSNumber *tagId;
  44. @property(nonatomic, strong) id<FSyncTreeHash> syncTreeHash;
  45. @property(nonatomic, copy) fbt_void_nsstring onComplete;
  46. @end
  47. @implementation FOutstandingQuery
  48. @end
  49. @interface FOutstandingPut : NSObject
  50. @property(nonatomic, strong) NSString *action;
  51. @property(nonatomic, strong) NSDictionary *request;
  52. @property(nonatomic, copy) fbt_void_nsstring_nsstring onCompleteBlock;
  53. @property(nonatomic) BOOL sent;
  54. @end
  55. @implementation FOutstandingPut
  56. @end
  57. @interface FOutstandingGet : NSObject
  58. @property(nonatomic, strong) NSDictionary *request;
  59. @property(nonatomic, copy) fbt_void_nsstring_id_nsstring onCompleteBlock;
  60. @property(nonatomic) BOOL sent;
  61. @end
  62. @implementation FOutstandingGet
  63. @end
  64. typedef enum {
  65. ConnectionStateDisconnected,
  66. ConnectionStateGettingToken,
  67. ConnectionStateConnecting,
  68. ConnectionStateAuthenticating,
  69. ConnectionStateConnected
  70. } ConnectionState;
  71. @interface FPersistentConnection () {
  72. ConnectionState connectionState;
  73. BOOL firstConnection;
  74. NSTimeInterval reconnectDelay;
  75. NSTimeInterval lastConnectionAttemptTime;
  76. NSTimeInterval lastConnectionEstablishedTime;
  77. #if !TARGET_OS_WATCH
  78. SCNetworkReachabilityRef reachability;
  79. #endif // !TARGET_OS_WATCH
  80. }
  81. - (int)getNextRequestNumber;
  82. - (void)onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body;
  83. - (void)handleTimestamp:(NSNumber *)timestamp;
  84. - (void)sendOnDisconnectAction:(NSString *)action
  85. forPath:(NSString *)pathString
  86. withData:(id)data
  87. andCallback:(fbt_void_nsstring_nsstring)callback;
  88. @property(nonatomic, strong) FConnection *realtime;
  89. @property(nonatomic, strong) NSMutableDictionary *listens;
  90. @property(nonatomic, strong) NSMutableDictionary *outstandingPuts;
  91. @property(nonatomic, strong) NSMutableDictionary *outstandingGets;
  92. @property(nonatomic, strong) NSMutableArray *onDisconnectQueue;
  93. @property(nonatomic, strong) FRepoInfo *repoInfo;
  94. @property(nonatomic, strong) FAtomicNumber *putCounter;
  95. @property(nonatomic, strong) FAtomicNumber *getCounter;
  96. @property(nonatomic, strong) FAtomicNumber *requestNumber;
  97. @property(nonatomic, strong) NSMutableDictionary *requestCBHash;
  98. @property(nonatomic, strong) FIRDatabaseConfig *config;
  99. @property(nonatomic) NSUInteger unackedListensCount;
  100. @property(nonatomic, strong) NSMutableArray *putsToAck;
  101. @property(nonatomic, strong) dispatch_queue_t dispatchQueue;
  102. @property(nonatomic, strong) NSString *lastSessionID;
  103. @property(nonatomic, strong) NSMutableSet *interruptReasons;
  104. @property(nonatomic, strong) FIRRetryHelper *retryHelper;
  105. @property(nonatomic, strong) id<FIRDatabaseConnectionContextProvider>
  106. contextProvider;
  107. @property(nonatomic, strong) NSString *authToken;
  108. @property(nonatomic) BOOL forceTokenRefreshes;
  109. @property(nonatomic) NSUInteger currentFetchTokenAttempt;
  110. @end
  111. @implementation FPersistentConnection
  112. - (id)initWithRepoInfo:(FRepoInfo *)repoInfo
  113. dispatchQueue:(dispatch_queue_t)dispatchQueue
  114. config:(FIRDatabaseConfig *)config {
  115. self = [super init];
  116. if (self) {
  117. self->_config = config;
  118. self->_repoInfo = repoInfo;
  119. self->_dispatchQueue = dispatchQueue;
  120. self->_contextProvider = config.contextProvider;
  121. NSAssert(self->_contextProvider != nil,
  122. @"Expected auth token provider");
  123. self.interruptReasons = [NSMutableSet set];
  124. self.listens = [[NSMutableDictionary alloc] init];
  125. self.outstandingPuts = [[NSMutableDictionary alloc] init];
  126. self.outstandingGets = [[NSMutableDictionary alloc] init];
  127. self.onDisconnectQueue = [[NSMutableArray alloc] init];
  128. self.putCounter = [[FAtomicNumber alloc] init];
  129. self.getCounter = [[FAtomicNumber alloc] init];
  130. self.requestNumber = [[FAtomicNumber alloc] init];
  131. self.requestCBHash = [[NSMutableDictionary alloc] init];
  132. self.unackedListensCount = 0;
  133. self.putsToAck = [NSMutableArray array];
  134. connectionState = ConnectionStateDisconnected;
  135. firstConnection = YES;
  136. reconnectDelay = kPersistentConnReconnectMinDelay;
  137. self->_retryHelper = [[FIRRetryHelper alloc]
  138. initWithDispatchQueue:dispatchQueue
  139. minRetryDelayAfterFailure:kPersistentConnReconnectMinDelay
  140. maxRetryDelay:kPersistentConnReconnectMaxDelay
  141. retryExponent:kPersistentConnReconnectMultiplier
  142. jitterFactor:0.7];
  143. // Make sure we don't actually connect until open is called
  144. [self interruptForReason:kFInterruptReasonWaitingForOpen];
  145. }
  146. // nb: The reason establishConnection isn't called here like the JS version
  147. // is because callers need to set the delegate first. The ctor can be
  148. // modified to accept the delegate but that deviates from normal ios
  149. // conventions. After the delegate has been set, the caller is responsible
  150. // for calling establishConnection:
  151. return self;
  152. }
  153. - (void)dealloc {
  154. #if !TARGET_OS_WATCH
  155. if (reachability) {
  156. // Unschedule the notifications
  157. SCNetworkReachabilitySetDispatchQueue(reachability, NULL);
  158. CFRelease(reachability);
  159. }
  160. #endif // !TARGET_OS_WATCH
  161. }
  162. #pragma mark -
  163. #pragma mark Public methods
  164. - (void)open {
  165. [self resumeForReason:kFInterruptReasonWaitingForOpen];
  166. }
  167. /**
  168. * Note that the listens dictionary has a type of Map[String (pathString),
  169. * Map[FQueryParams, FOutstandingQuery]]
  170. *
  171. * This means, for each path we care about, there are sets of queryParams that
  172. * correspond to an FOutstandingQuery object. There can be multiple sets at a
  173. * path since we overlap listens for a short time while adding or removing a
  174. * query from a location in the tree.
  175. */
  176. - (void)listen:(FQuerySpec *)query
  177. tagId:(NSNumber *)tagId
  178. hash:(id<FSyncTreeHash>)hash
  179. onComplete:(fbt_void_nsstring)onComplete {
  180. FFLog(@"I-RDB034001", @"Listen called for %@", query);
  181. NSAssert(self.listens[query] == nil,
  182. @"listen() called twice for the same query");
  183. NSAssert(query.isDefault || !query.loadsAllData,
  184. @"listen called for non-default but complete query");
  185. FOutstandingQuery *outstanding = [[FOutstandingQuery alloc] init];
  186. outstanding.query = query;
  187. outstanding.tagId = tagId;
  188. outstanding.syncTreeHash = hash;
  189. outstanding.onComplete = onComplete;
  190. [self.listens setObject:outstanding forKey:query];
  191. if ([self connected]) {
  192. [self sendListen:outstanding];
  193. }
  194. }
  195. - (void)putData:(id)data
  196. forPath:(NSString *)pathString
  197. withHash:(NSString *)hash
  198. withCallback:(fbt_void_nsstring_nsstring)onComplete {
  199. [self putInternal:data
  200. forAction:kFWPRequestActionPut
  201. forPath:pathString
  202. withHash:hash
  203. withCallback:onComplete];
  204. }
  205. - (void)mergeData:(id)data
  206. forPath:(NSString *)pathString
  207. withCallback:(fbt_void_nsstring_nsstring)onComplete {
  208. [self putInternal:data
  209. forAction:kFWPRequestActionMerge
  210. forPath:pathString
  211. withHash:nil
  212. withCallback:onComplete];
  213. }
  214. - (void)onDisconnectPutData:(id)data
  215. forPath:(FPath *)path
  216. withCallback:(fbt_void_nsstring_nsstring)callback {
  217. if ([self canSendWrites]) {
  218. [self sendOnDisconnectAction:kFWPRequestActionDisconnectPut
  219. forPath:[path description]
  220. withData:data
  221. andCallback:callback];
  222. } else {
  223. FTupleOnDisconnect *tuple = [[FTupleOnDisconnect alloc] init];
  224. tuple.pathString = [path description];
  225. tuple.action = kFWPRequestActionDisconnectPut;
  226. tuple.data = data;
  227. tuple.onComplete = callback;
  228. [self.onDisconnectQueue addObject:tuple];
  229. }
  230. }
  231. - (void)onDisconnectMergeData:(id)data
  232. forPath:(FPath *)path
  233. withCallback:(fbt_void_nsstring_nsstring)callback {
  234. if ([self canSendWrites]) {
  235. [self sendOnDisconnectAction:kFWPRequestActionDisconnectMerge
  236. forPath:[path description]
  237. withData:data
  238. andCallback:callback];
  239. } else {
  240. FTupleOnDisconnect *tuple = [[FTupleOnDisconnect alloc] init];
  241. tuple.pathString = [path description];
  242. tuple.action = kFWPRequestActionDisconnectMerge;
  243. tuple.data = data;
  244. tuple.onComplete = callback;
  245. [self.onDisconnectQueue addObject:tuple];
  246. }
  247. }
  248. - (void)onDisconnectCancelPath:(FPath *)path
  249. withCallback:(fbt_void_nsstring_nsstring)callback {
  250. if ([self canSendWrites]) {
  251. [self sendOnDisconnectAction:kFWPRequestActionDisconnectCancel
  252. forPath:[path description]
  253. withData:[NSNull null]
  254. andCallback:callback];
  255. } else {
  256. FTupleOnDisconnect *tuple = [[FTupleOnDisconnect alloc] init];
  257. tuple.pathString = [path description];
  258. tuple.action = kFWPRequestActionDisconnectCancel;
  259. tuple.data = [NSNull null];
  260. tuple.onComplete = callback;
  261. [self.onDisconnectQueue addObject:tuple];
  262. }
  263. }
  264. - (void)unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId {
  265. FPath *path = query.path;
  266. FFLog(@"I-RDB034002", @"Unlistening for %@", query);
  267. NSArray *outstanding = [self removeListen:query];
  268. if (outstanding.count > 0 && [self connected]) {
  269. [self sendUnlisten:path queryParams:query.params tagId:tagId];
  270. }
  271. }
  272. - (void)refreshAuthToken:(NSString *)token {
  273. self.authToken = token;
  274. if ([self connected]) {
  275. if (token != nil) {
  276. [self sendAuthAndRestoreStateAfterComplete:NO];
  277. } else {
  278. [self sendUnauth];
  279. }
  280. }
  281. }
  282. #pragma mark -
  283. #pragma mark Connection status
  284. - (BOOL)connected {
  285. return self->connectionState == ConnectionStateAuthenticating ||
  286. self->connectionState == ConnectionStateConnected;
  287. }
  288. - (BOOL)canSendWrites {
  289. return self->connectionState == ConnectionStateConnected;
  290. }
  291. - (BOOL)canSendReads {
  292. return self->connectionState == ConnectionStateConnected;
  293. }
  294. #pragma mark -
  295. #pragma mark FConnection delegate methods
  296. - (void)onReady:(FConnection *)fconnection
  297. atTime:(NSNumber *)timestamp
  298. sessionID:(NSString *)sessionID {
  299. FFLog(@"I-RDB034003", @"On ready");
  300. lastConnectionEstablishedTime = [[NSDate date] timeIntervalSince1970];
  301. [self handleTimestamp:timestamp];
  302. if (firstConnection) {
  303. [self sendConnectStats];
  304. }
  305. [self restoreAuth];
  306. firstConnection = NO;
  307. self.lastSessionID = sessionID;
  308. dispatch_async(self.dispatchQueue, ^{
  309. [self.delegate onConnect:self];
  310. });
  311. }
  312. - (void)onDataMessage:(FConnection *)fconnection
  313. withMessage:(NSDictionary *)message {
  314. if (message[kFWPRequestNumber] != nil) {
  315. // this is a response to a request we sent
  316. NSNumber *rn = [NSNumber
  317. numberWithInt:[[message objectForKey:kFWPRequestNumber] intValue]];
  318. if ([self.requestCBHash objectForKey:rn]) {
  319. void (^callback)(NSDictionary *) =
  320. [self.requestCBHash objectForKey:rn];
  321. [self.requestCBHash removeObjectForKey:rn];
  322. if (callback) {
  323. // dispatch_async(self.dispatchQueue, ^{
  324. callback([message objectForKey:kFWPResponseForRNData]);
  325. //});
  326. }
  327. }
  328. } else if (message[kFWPRequestError] != nil) {
  329. NSString *error = [message objectForKey:kFWPRequestError];
  330. @throw [[NSException alloc] initWithName:@"FirebaseDatabaseServerError"
  331. reason:error
  332. userInfo:nil];
  333. } else if (message[kFWPAsyncServerAction] != nil) {
  334. // this is a server push of some sort
  335. NSString *action = [message objectForKey:kFWPAsyncServerAction];
  336. NSDictionary *body = [message objectForKey:kFWPAsyncServerPayloadBody];
  337. [self onDataPushWithAction:action andBody:body];
  338. }
  339. }
  340. - (void)onDisconnect:(FConnection *)fconnection
  341. withReason:(FDisconnectReason)reason {
  342. FFLog(@"I-RDB034004", @"Got on disconnect due to %s",
  343. (reason == DISCONNECT_REASON_SERVER_RESET) ? "server_reset"
  344. : "other");
  345. connectionState = ConnectionStateDisconnected;
  346. // Drop the realtime connection
  347. self.realtime = nil;
  348. [self cancelSentTransactions];
  349. [self.requestCBHash removeAllObjects];
  350. self.unackedListensCount = 0;
  351. if ([self shouldReconnect]) {
  352. NSTimeInterval timeSinceLastConnectSucceeded =
  353. [[NSDate date] timeIntervalSince1970] -
  354. lastConnectionEstablishedTime;
  355. BOOL lastConnectionWasSuccessful;
  356. if (lastConnectionEstablishedTime > 0) {
  357. lastConnectionWasSuccessful =
  358. timeSinceLastConnectSucceeded >
  359. kPersistentConnSuccessfulConnectionEstablishedDelay;
  360. } else {
  361. lastConnectionWasSuccessful = NO;
  362. }
  363. if (reason == DISCONNECT_REASON_SERVER_RESET ||
  364. lastConnectionWasSuccessful) {
  365. [self.retryHelper signalSuccess];
  366. }
  367. [self tryScheduleReconnect];
  368. }
  369. lastConnectionEstablishedTime = 0;
  370. [self.delegate onDisconnect:self];
  371. }
  372. - (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason {
  373. FFWarn(@"I-RDB034005",
  374. @"Firebase Database connection was forcefully killed by the server. "
  375. @" Will not attempt reconnect. Reason: %@",
  376. reason);
  377. [self interruptForReason:kFInterruptReasonServerKill];
  378. }
  379. #pragma mark -
  380. #pragma mark Connection handling methods
  381. - (void)interruptForReason:(NSString *)reason {
  382. FFLog(@"I-RDB034006", @"Connection interrupted for: %@", reason);
  383. [self.interruptReasons addObject:reason];
  384. if (self.realtime) {
  385. // Will call onDisconnect and set the connection state to Disconnected
  386. [self.realtime close];
  387. self.realtime = nil;
  388. } else {
  389. [self.retryHelper cancel];
  390. self->connectionState = ConnectionStateDisconnected;
  391. }
  392. // Reset timeouts
  393. [self.retryHelper signalSuccess];
  394. }
  395. - (void)resumeForReason:(NSString *)reason {
  396. FFLog(@"I-RDB034007", @"Connection no longer interrupted for: %@", reason);
  397. [self.interruptReasons removeObject:reason];
  398. if ([self shouldReconnect] &&
  399. connectionState == ConnectionStateDisconnected) {
  400. [self tryScheduleReconnect];
  401. }
  402. }
  403. - (BOOL)shouldReconnect {
  404. return self.interruptReasons.count == 0;
  405. }
  406. - (BOOL)isInterruptedForReason:(NSString *)reason {
  407. return [self.interruptReasons containsObject:reason];
  408. }
  409. #pragma mark -
  410. #pragma mark Private methods
  411. - (void)tryScheduleReconnect {
  412. if ([self shouldReconnect]) {
  413. NSAssert(self->connectionState == ConnectionStateDisconnected,
  414. @"Not in disconnected state: %d", self->connectionState);
  415. BOOL forceRefresh = self.forceTokenRefreshes;
  416. self.forceTokenRefreshes = NO;
  417. FFLog(@"I-RDB034008", @"Scheduling connection attempt");
  418. [self.retryHelper retry:^{
  419. FFLog(@"I-RDB034009", @"Trying to fetch auth token");
  420. NSAssert(self->connectionState == ConnectionStateDisconnected,
  421. @"Not in disconnected state: %d", self->connectionState);
  422. self->connectionState = ConnectionStateGettingToken;
  423. self.currentFetchTokenAttempt++;
  424. NSUInteger thisFetchTokenAttempt = self.currentFetchTokenAttempt;
  425. [self.contextProvider
  426. fetchContextForcingRefresh:forceRefresh
  427. withCallback:^(
  428. FIRDatabaseConnectionContext *context,
  429. NSError *error) {
  430. if (thisFetchTokenAttempt ==
  431. self.currentFetchTokenAttempt) {
  432. if (error != nil) {
  433. self->connectionState =
  434. ConnectionStateDisconnected;
  435. FFLog(@"I-RDB034010",
  436. @"Error fetching token: %@", error);
  437. [self tryScheduleReconnect];
  438. } else {
  439. // Someone could have interrupted us while
  440. // fetching the token, marking the
  441. // connection as Disconnected
  442. if (self->connectionState ==
  443. ConnectionStateGettingToken) {
  444. FFLog(@"I-RDB034011",
  445. @"Successfully fetched token, "
  446. @"opening connection");
  447. [self
  448. openNetworkConnectionWithContext:
  449. context];
  450. } else {
  451. NSAssert(
  452. self->connectionState ==
  453. ConnectionStateDisconnected,
  454. @"Expected connection state "
  455. @"disconnected, but got %d",
  456. self->connectionState);
  457. FFLog(@"I-RDB034012",
  458. @"Not opening connection after "
  459. @"token refresh, because "
  460. @"connection was set to "
  461. @"disconnected.");
  462. }
  463. }
  464. } else {
  465. FFLog(@"I-RDB034013",
  466. @"Ignoring fetch token result, because "
  467. @"this was not the latest attempt.");
  468. }
  469. }];
  470. }];
  471. }
  472. }
  473. - (void)openNetworkConnectionWithContext:
  474. (FIRDatabaseConnectionContext *)context {
  475. NSAssert(self->connectionState == ConnectionStateGettingToken,
  476. @"Trying to open network connection while in wrong state: %d",
  477. self->connectionState);
  478. // TODO: Save entire context?
  479. self.authToken = context.authToken;
  480. self->connectionState = ConnectionStateConnecting;
  481. self.realtime = [[FConnection alloc] initWith:self.repoInfo
  482. andDispatchQueue:self.dispatchQueue
  483. googleAppID:self.config.googleAppID
  484. lastSessionID:self.lastSessionID
  485. appCheckToken:context.appCheckToken];
  486. self.realtime.delegate = self;
  487. [self.realtime open];
  488. }
  489. - (void)sendAuthAndRestoreStateAfterComplete:(BOOL)restoreStateAfterComplete {
  490. NSAssert([self connected], @"Must be connected to send auth");
  491. NSAssert(self.authToken != nil,
  492. @"Can't send auth if there is no credential");
  493. NSDictionary *requestData = @{kFWPRequestCredential : self.authToken};
  494. [self sendAction:kFWPRequestActionAuth
  495. body:requestData
  496. sensitive:YES
  497. callback:^(NSDictionary *data) {
  498. self->connectionState = ConnectionStateConnected;
  499. NSString *status =
  500. [data objectForKey:kFWPResponseForActionStatus];
  501. id responseData = [data objectForKey:kFWPResponseForActionData];
  502. if (responseData == nil) {
  503. responseData = @"error";
  504. }
  505. BOOL statusOk =
  506. [status isEqualToString:kFWPResponseForActionStatusOk];
  507. if (statusOk) {
  508. if (restoreStateAfterComplete) {
  509. [self restoreState];
  510. }
  511. } else {
  512. self.authToken = nil;
  513. self.forceTokenRefreshes = YES;
  514. if ([status isEqualToString:@"expired_token"]) {
  515. FFLog(@"I-RDB034017", @"Authentication failed: %@ (%@)",
  516. status, responseData);
  517. } else {
  518. FFWarn(@"I-RDB034018", @"Authentication failed: %@ (%@)",
  519. status, responseData);
  520. }
  521. [self.realtime close];
  522. }
  523. }];
  524. }
  525. - (void)sendUnauth {
  526. [self sendAction:kFWPRequestActionUnauth
  527. body:@{}
  528. sensitive:NO
  529. callback:nil];
  530. }
  531. - (void)onAuthRevokedWithStatus:(NSString *)status
  532. andReason:(NSString *)reason {
  533. // This might be for an earlier token than we just recently sent. But since
  534. // we need to close the connection anyways, we can set it to null here and
  535. // we will refresh the token later on reconnect
  536. if ([status isEqualToString:@"expired_token"]) {
  537. FFLog(@"I-RDB034019", @"Auth token revoked: %@ (%@)", status, reason);
  538. } else {
  539. FFWarn(@"I-RDB034020", @"Auth token revoked: %@ (%@)", status, reason);
  540. }
  541. self.authToken = nil;
  542. self.forceTokenRefreshes = YES;
  543. // Try reconnecting on auth revocation
  544. [self.realtime close];
  545. }
  546. - (void)onListenRevoked:(FPath *)path {
  547. NSArray *queries = [self removeAllListensAtPath:path];
  548. for (FOutstandingQuery *query in queries) {
  549. query.onComplete(@"permission_denied");
  550. }
  551. }
  552. - (void)sendOnDisconnectAction:(NSString *)action
  553. forPath:(NSString *)pathString
  554. withData:(id)data
  555. andCallback:(fbt_void_nsstring_nsstring)callback {
  556. NSDictionary *request =
  557. @{kFWPRequestPath : pathString, kFWPRequestData : data};
  558. FFLog(@"I-RDB034021", @"onDisconnect %@: %@", action, request);
  559. [self sendAction:action
  560. body:request
  561. sensitive:NO
  562. callback:^(NSDictionary *data) {
  563. NSString *status =
  564. [data objectForKey:kFWPResponseForActionStatus];
  565. NSString *errorReason =
  566. [data objectForKey:kFWPResponseForActionData];
  567. callback(status, errorReason);
  568. }];
  569. }
  570. - (void)sendPut:(NSNumber *)index {
  571. NSAssert([self canSendWrites],
  572. @"sendPut called when not able to send writes");
  573. FOutstandingPut *put = self.outstandingPuts[index];
  574. assert(put != nil);
  575. fbt_void_nsstring_nsstring onComplete = put.onCompleteBlock;
  576. // Do not async this block; copying the block insinde sendAction: doesn't
  577. // happen in time (or something) so coredumps
  578. put.sent = YES;
  579. [self sendAction:put.action
  580. body:put.request
  581. sensitive:NO
  582. callback:^(NSDictionary *data) {
  583. FOutstandingPut *currentPut = self.outstandingPuts[index];
  584. if (currentPut == put) {
  585. [self.outstandingPuts removeObjectForKey:index];
  586. if (onComplete != nil) {
  587. NSString *status =
  588. [data objectForKey:kFWPResponseForActionStatus];
  589. NSString *errorReason =
  590. [data objectForKey:kFWPResponseForActionData];
  591. if (self.unackedListensCount == 0) {
  592. onComplete(status, errorReason);
  593. } else {
  594. FTupleCallbackStatus *putToAck =
  595. [[FTupleCallbackStatus alloc] init];
  596. putToAck.block = onComplete;
  597. putToAck.status = status;
  598. putToAck.errorReason = errorReason;
  599. [self.putsToAck addObject:putToAck];
  600. }
  601. }
  602. } else {
  603. FFLog(@"I-RDB034022",
  604. @"Ignoring on complete for put %@ because it was "
  605. @"already removed",
  606. index);
  607. }
  608. }];
  609. }
  610. - (void)sendGet:(NSNumber *)index {
  611. NSAssert([self canSendReads],
  612. @"sendGet called when not able to send reads");
  613. FOutstandingGet *get = self.outstandingGets[index];
  614. NSAssert(get != nil, @"sendGet found no outstanding get at index %@",
  615. index);
  616. if ([get sent]) {
  617. return;
  618. }
  619. get.sent = YES;
  620. [self sendAction:kFWPRequestActionGet
  621. body:get.request
  622. sensitive:NO
  623. callback:^(NSDictionary *data) {
  624. FOutstandingGet *currentGet = self.outstandingGets[index];
  625. if (currentGet == get) {
  626. [self.outstandingGets removeObjectForKey:index];
  627. NSString *status =
  628. [data objectForKey:kFWPResponseForActionStatus];
  629. id resultData = [data objectForKey:kFWPResponseForActionData];
  630. if (resultData == (id)[NSNull null]) {
  631. resultData = nil;
  632. }
  633. if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
  634. get.onCompleteBlock(status, resultData, nil);
  635. return;
  636. }
  637. get.onCompleteBlock(status, nil, resultData);
  638. } else {
  639. FFLog(@"I-RDB034045",
  640. @"Ignoring on complete for get %@ because it was "
  641. @"already removed",
  642. index);
  643. }
  644. }];
  645. }
  646. - (void)sendUnlisten:(FPath *)path
  647. queryParams:(FQueryParams *)queryParams
  648. tagId:(NSNumber *)tagId {
  649. FFLog(@"I-RDB034023", @"Unlisten on %@ for %@", path, queryParams);
  650. NSMutableDictionary *request = [NSMutableDictionary
  651. dictionaryWithObjectsAndKeys:[path toString], kFWPRequestPath, nil];
  652. if (tagId != nil) {
  653. [request setObject:queryParams.wireProtocolParams
  654. forKey:kFWPRequestQueries];
  655. [request setObject:tagId forKey:kFWPRequestTag];
  656. }
  657. [self sendAction:kFWPRequestActionTaggedUnlisten
  658. body:request
  659. sensitive:NO
  660. callback:nil];
  661. }
  662. - (void)putInternal:(id)data
  663. forAction:(NSString *)action
  664. forPath:(NSString *)pathString
  665. withHash:(NSString *)hash
  666. withCallback:(fbt_void_nsstring_nsstring)onComplete {
  667. NSMutableDictionary *request = [NSMutableDictionary
  668. dictionaryWithObjectsAndKeys:pathString, kFWPRequestPath, data,
  669. kFWPRequestData, nil];
  670. if (hash) {
  671. [request setObject:hash forKey:kFWPRequestHash];
  672. }
  673. FOutstandingPut *put = [[FOutstandingPut alloc] init];
  674. put.action = action;
  675. put.request = request;
  676. put.onCompleteBlock = onComplete;
  677. put.sent = NO;
  678. NSNumber *index = [self.putCounter getAndIncrement];
  679. self.outstandingPuts[index] = put;
  680. if ([self canSendWrites]) {
  681. FFLog(@"I-RDB034024", @"Was connected, and added as index: %@", index);
  682. [self sendPut:index];
  683. } else {
  684. FFLog(@"I-RDB034025",
  685. @"Wasn't connected or writes paused, so added to outstanding "
  686. @"puts only. Path: %@",
  687. pathString);
  688. }
  689. }
  690. - (void)getDataAtPath:(NSString *)pathString
  691. withParams:(NSDictionary *)queryWireProtocolParams
  692. withCallback:(fbt_void_nsstring_id_nsstring)onComplete {
  693. NSMutableDictionary *request = [NSMutableDictionary
  694. dictionaryWithObjectsAndKeys:pathString, kFWPRequestPath,
  695. queryWireProtocolParams,
  696. kFWPRequestQueries, nil];
  697. FOutstandingGet *get = [[FOutstandingGet alloc] init];
  698. get.request = request;
  699. get.onCompleteBlock = onComplete;
  700. get.sent = NO;
  701. NSNumber *index = [self.getCounter getAndIncrement];
  702. self.outstandingGets[index] = get;
  703. if (![self connected]) {
  704. dispatch_after(
  705. dispatch_time(DISPATCH_TIME_NOW,
  706. kPersistentConnectionGetConnectTimeout),
  707. self.dispatchQueue, ^{
  708. FOutstandingGet *currGet = self.outstandingGets[index];
  709. if ([currGet sent] || currGet == nil) {
  710. return;
  711. }
  712. FFLog(@"I-RDB034045",
  713. @"get %@ timed out waiting for a connection", index);
  714. currGet.sent = YES;
  715. currGet.onCompleteBlock(kFWPResponseForActionStatusFailed, nil,
  716. kPersistentConnectionOffline);
  717. [self.outstandingGets removeObjectForKey:index];
  718. });
  719. return;
  720. }
  721. if ([self canSendReads]) {
  722. FFLog(@"I-RDB034024", @"Sending get: %@", index);
  723. [self sendGet:index];
  724. }
  725. }
  726. - (void)sendListen:(FOutstandingQuery *)listenSpec {
  727. FQuerySpec *query = listenSpec.query;
  728. FFLog(@"I-RDB034026", @"Listen for %@", query);
  729. NSMutableDictionary *request =
  730. [NSMutableDictionary dictionaryWithObject:[query.path toString]
  731. forKey:kFWPRequestPath];
  732. // Only bother to send query if it's non-default
  733. if (listenSpec.tagId != nil) {
  734. [request setObject:[query.params wireProtocolParams]
  735. forKey:kFWPRequestQueries];
  736. [request setObject:listenSpec.tagId forKey:kFWPRequestTag];
  737. }
  738. [request setObject:[listenSpec.syncTreeHash simpleHash]
  739. forKey:kFWPRequestHash];
  740. if ([listenSpec.syncTreeHash includeCompoundHash]) {
  741. FCompoundHash *compoundHash = [listenSpec.syncTreeHash compoundHash];
  742. NSMutableArray *posts = [NSMutableArray array];
  743. for (FPath *path in compoundHash.posts) {
  744. [posts addObject:path.wireFormat];
  745. }
  746. request[kFWPRequestCompoundHash] = @{
  747. kFWPRequestCompoundHashHashes : compoundHash.hashes,
  748. kFWPRequestCompoundHashPaths : posts
  749. };
  750. }
  751. fbt_void_nsdictionary onResponse = ^(NSDictionary *response) {
  752. FFLog(@"I-RDB034027", @"Listen response %@", response);
  753. // warn in any case, even if the listener was removed
  754. [self warnOnListenWarningsForQuery:query
  755. payload:response[kFWPResponseForActionData]];
  756. FOutstandingQuery *currentListenSpec = self.listens[query];
  757. // only trigger actions if the listen hasn't been removed (and maybe
  758. // readded)
  759. if (currentListenSpec == listenSpec) {
  760. NSString *status = [response objectForKey:kFWPRequestStatus];
  761. if (![status isEqualToString:@"ok"]) {
  762. [self removeListen:query];
  763. }
  764. if (listenSpec.onComplete) {
  765. listenSpec.onComplete(status);
  766. }
  767. }
  768. self.unackedListensCount--;
  769. NSAssert(self.unackedListensCount >= 0,
  770. @"unackedListensCount decremented to be negative.");
  771. if (self.unackedListensCount == 0) {
  772. [self ackPuts];
  773. }
  774. };
  775. [self sendAction:kFWPRequestActionTaggedListen
  776. body:request
  777. sensitive:NO
  778. callback:onResponse];
  779. self.unackedListensCount++;
  780. }
  781. - (void)warnOnListenWarningsForQuery:(FQuerySpec *)query payload:(id)payload {
  782. if (payload != nil && [payload isKindOfClass:[NSDictionary class]]) {
  783. NSDictionary *payloadDict = payload;
  784. id warnings = payloadDict[kFWPResponseDataWarnings];
  785. if (warnings != nil && [warnings isKindOfClass:[NSArray class]]) {
  786. NSArray *warningsArr = warnings;
  787. if ([warningsArr containsObject:@"no_index"]) {
  788. NSString *indexSpec = [NSString
  789. stringWithFormat:@"\".indexOn\": \"%@\"",
  790. [query.params.index queryDefinition]];
  791. NSString *indexPath = [query.path description];
  792. FFWarn(@"I-RDB034028",
  793. @"Using an unspecified index. Your data will be "
  794. @"downloaded and filtered on the client. "
  795. "Consider adding %@ at %@ to your security rules for "
  796. "better performance",
  797. indexSpec, indexPath);
  798. }
  799. }
  800. }
  801. }
  802. - (int)getNextRequestNumber {
  803. return [[self.requestNumber getAndIncrement] intValue];
  804. }
  805. - (void)sendAction:(NSString *)action
  806. body:(NSDictionary *)message
  807. sensitive:(BOOL)sensitive
  808. callback:(void (^)(NSDictionary *data))onMessage {
  809. // Hold onto the onMessage callback for this request before firing it off
  810. NSNumber *rn = [NSNumber numberWithInt:[self getNextRequestNumber]];
  811. NSDictionary *msg = [NSDictionary
  812. dictionaryWithObjectsAndKeys:rn, kFWPRequestNumber, action,
  813. kFWPRequestAction, message,
  814. kFWPRequestPayloadBody, nil];
  815. [self.realtime sendRequest:msg sensitive:sensitive];
  816. if (onMessage) {
  817. // Debug message without a callback; bump the rn, but don't hold onto
  818. // the cb
  819. [self.requestCBHash setObject:[onMessage copy] forKey:rn];
  820. }
  821. }
  822. - (void)cancelSentTransactions {
  823. NSMutableDictionary<NSNumber *, FOutstandingPut *>
  824. *cancelledOutstandingPuts = [[NSMutableDictionary alloc] init];
  825. for (NSNumber *index in self.outstandingPuts) {
  826. FOutstandingPut *put = self.outstandingPuts[index];
  827. if (put.request[kFWPRequestHash] && put.sent) {
  828. // This is a sent transaction put.
  829. cancelledOutstandingPuts[index] = put;
  830. }
  831. }
  832. [cancelledOutstandingPuts
  833. enumerateKeysAndObjectsUsingBlock:^(
  834. NSNumber *index, FOutstandingPut *outstandingPut, BOOL *stop) {
  835. // `onCompleteBlock:` may invoke `rerunTransactionsForPath:` and
  836. // enqueue new writes. We defer calling it until we have finished
  837. // enumerating all existing writes.
  838. outstandingPut.onCompleteBlock(
  839. kFTransactionDisconnect,
  840. @"Client was disconnected while running a transaction");
  841. [self.outstandingPuts removeObjectForKey:index];
  842. }];
  843. }
  844. - (void)onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body {
  845. FFLog(@"I-RDB034029", @"handleServerMessage: %@, %@", action, body);
  846. id<FPersistentConnectionDelegate> delegate = self.delegate;
  847. if ([action isEqualToString:kFWPAsyncServerDataUpdate] ||
  848. [action isEqualToString:kFWPAsyncServerDataMerge]) {
  849. BOOL isMerge = [action isEqualToString:kFWPAsyncServerDataMerge];
  850. if ([body objectForKey:kFWPAsyncServerDataUpdateBodyPath] &&
  851. [body objectForKey:kFWPAsyncServerDataUpdateBodyData]) {
  852. NSString *path =
  853. [body objectForKey:kFWPAsyncServerDataUpdateBodyPath];
  854. id payloadData =
  855. [body objectForKey:kFWPAsyncServerDataUpdateBodyData];
  856. if (isMerge && [payloadData isKindOfClass:[NSDictionary class]] &&
  857. [payloadData count] == 0) {
  858. // ignore empty merge
  859. } else {
  860. [delegate
  861. onDataUpdate:self
  862. forPath:path
  863. message:payloadData
  864. isMerge:isMerge
  865. tagId:[body objectForKey:
  866. kFWPAsyncServerDataUpdateBodyTag]];
  867. }
  868. } else {
  869. FFLog(
  870. @"I-RDB034030",
  871. @"Malformed data response from server missing path or data: %@",
  872. body);
  873. }
  874. } else if ([action isEqualToString:kFWPAsyncServerDataRangeMerge]) {
  875. NSString *path = body[kFWPAsyncServerDataUpdateBodyPath];
  876. NSArray *ranges = body[kFWPAsyncServerDataUpdateBodyData];
  877. NSNumber *tag = body[kFWPAsyncServerDataUpdateBodyTag];
  878. NSMutableArray *rangeMerges = [NSMutableArray array];
  879. for (NSDictionary *range in ranges) {
  880. NSString *startString = range[kFWPAsyncServerDataUpdateStartPath];
  881. NSString *endString = range[kFWPAsyncServerDataUpdateEndPath];
  882. id updateData = range[kFWPAsyncServerDataUpdateRangeMerge];
  883. id<FNode> updates = [FSnapshotUtilities nodeFrom:updateData];
  884. FPath *start = (startString != nil)
  885. ? [[FPath alloc] initWith:startString]
  886. : nil;
  887. FPath *end =
  888. (endString != nil) ? [[FPath alloc] initWith:endString] : nil;
  889. FRangeMerge *merge = [[FRangeMerge alloc] initWithStart:start
  890. end:end
  891. updates:updates];
  892. [rangeMerges addObject:merge];
  893. }
  894. [delegate onRangeMerge:rangeMerges forPath:path tagId:tag];
  895. } else if ([action isEqualToString:kFWPAsyncServerAuthRevoked]) {
  896. NSString *status = [body objectForKey:kFWPResponseForActionStatus];
  897. NSString *reason = [body objectForKey:kFWPResponseForActionData];
  898. [self onAuthRevokedWithStatus:status andReason:reason];
  899. } else if ([action isEqualToString:kFWPASyncServerListenCancelled]) {
  900. NSString *pathString =
  901. [body objectForKey:kFWPAsyncServerDataUpdateBodyPath];
  902. [self onListenRevoked:[[FPath alloc] initWith:pathString]];
  903. } else if ([action isEqualToString:kFWPAsyncServerSecurityDebug]) {
  904. NSString *msg = [body objectForKey:@"msg"];
  905. if (msg != nil) {
  906. NSArray *msgs = [msg componentsSeparatedByString:@"\n"];
  907. for (NSString *m in msgs) {
  908. FFWarn(@"I-RDB034031", @"%@", m);
  909. }
  910. }
  911. } else {
  912. // TODO: revoke listens, auth, security debug
  913. FFLog(@"I-RDB034032", @"Unsupported action from server: %@", action);
  914. }
  915. }
  916. - (void)restoreAuth {
  917. FFLog(@"I-RDB034033", @"Calling restore state");
  918. NSAssert(self->connectionState == ConnectionStateConnecting,
  919. @"Wanted to restore auth, but was in wrong state: %d",
  920. self->connectionState);
  921. if (self.authToken == nil) {
  922. FFLog(@"I-RDB034034", @"Not restoring auth because token is nil");
  923. self->connectionState = ConnectionStateConnected;
  924. [self restoreState];
  925. } else {
  926. FFLog(@"I-RDB034035", @"Restoring auth");
  927. self->connectionState = ConnectionStateAuthenticating;
  928. [self sendAuthAndRestoreStateAfterComplete:YES];
  929. }
  930. }
  931. - (void)restoreState {
  932. NSAssert(self->connectionState == ConnectionStateConnected,
  933. @"Should be connected if we're restoring state, but we are: %d",
  934. self->connectionState);
  935. [self.listens enumerateKeysAndObjectsUsingBlock:^(
  936. FQuerySpec *query, FOutstandingQuery *outstandingListen,
  937. BOOL *stop) {
  938. FFLog(@"I-RDB034036", @"Restoring listen for %@", query);
  939. [self sendListen:outstandingListen];
  940. }];
  941. NSArray *putKeys = [[self.outstandingPuts allKeys]
  942. sortedArrayUsingSelector:@selector(compare:)];
  943. for (int i = 0; i < [putKeys count]; i++) {
  944. if ([self.outstandingPuts objectForKey:[putKeys objectAtIndex:i]] !=
  945. nil) {
  946. FFLog(@"I-RDB034037", @"Restoring put: %d", i);
  947. [self sendPut:[putKeys objectAtIndex:i]];
  948. } else {
  949. FFLog(@"I-RDB034038", @"Restoring put: skipped nil: %d", i);
  950. }
  951. }
  952. NSArray *getKeys = [[self.outstandingGets allKeys]
  953. sortedArrayUsingSelector:@selector(compare:)];
  954. for (int i = 0; i < [getKeys count]; i++) {
  955. if ([self.outstandingGets objectForKey:[getKeys objectAtIndex:i]] !=
  956. nil) {
  957. FFLog(@"I-RDB034037", @"Restoring get: %d", i);
  958. [self sendGet:[getKeys objectAtIndex:i]];
  959. } else {
  960. FFLog(@"I-RDB034038", @"Restoring get: skipped nil: %d", i);
  961. }
  962. }
  963. for (FTupleOnDisconnect *tuple in self.onDisconnectQueue) {
  964. [self sendOnDisconnectAction:tuple.action
  965. forPath:tuple.pathString
  966. withData:tuple.data
  967. andCallback:tuple.onComplete];
  968. }
  969. [self.onDisconnectQueue removeAllObjects];
  970. }
  971. - (NSArray *)removeListen:(FQuerySpec *)query {
  972. NSAssert(query.isDefault || !query.loadsAllData,
  973. @"removeListen called for non-default but complete query");
  974. FOutstandingQuery *outstanding = self.listens[query];
  975. if (!outstanding) {
  976. FFLog(@"I-RDB034039",
  977. @"Trying to remove listener for query %@ but no listener exists",
  978. query);
  979. return @[];
  980. } else {
  981. [self.listens removeObjectForKey:query];
  982. return @[ outstanding ];
  983. }
  984. }
  985. - (NSArray *)removeAllListensAtPath:(FPath *)path {
  986. FFLog(@"I-RDB034040", @"Removing all listens at path %@", path);
  987. NSMutableArray *removed = [NSMutableArray array];
  988. NSMutableArray *toRemove = [NSMutableArray array];
  989. [self.listens
  990. enumerateKeysAndObjectsUsingBlock:^(
  991. FQuerySpec *spec, FOutstandingQuery *outstanding, BOOL *stop) {
  992. if ([spec.path isEqual:path]) {
  993. [removed addObject:outstanding];
  994. [toRemove addObject:spec];
  995. }
  996. }];
  997. [self.listens removeObjectsForKeys:toRemove];
  998. return removed;
  999. }
  1000. - (void)purgeOutstandingWrites {
  1001. // We might have unacked puts in our queue that we need to ack now before we
  1002. // send out any cancels...
  1003. [self ackPuts];
  1004. // Cancel in order
  1005. NSArray *keys = [[self.outstandingPuts allKeys]
  1006. sortedArrayUsingSelector:@selector(compare:)];
  1007. for (NSNumber *key in keys) {
  1008. FOutstandingPut *put = self.outstandingPuts[key];
  1009. if (put.onCompleteBlock != nil) {
  1010. put.onCompleteBlock(kFErrorWriteCanceled, nil);
  1011. }
  1012. }
  1013. for (FTupleOnDisconnect *onDisconnect in self.onDisconnectQueue) {
  1014. if (onDisconnect.onComplete != nil) {
  1015. onDisconnect.onComplete(kFErrorWriteCanceled, nil);
  1016. }
  1017. }
  1018. [self.outstandingPuts removeAllObjects];
  1019. [self.onDisconnectQueue removeAllObjects];
  1020. }
  1021. - (void)ackPuts {
  1022. for (FTupleCallbackStatus *put in self.putsToAck) {
  1023. put.block(put.status, put.errorReason);
  1024. }
  1025. [self.putsToAck removeAllObjects];
  1026. }
  1027. - (void)handleTimestamp:(NSNumber *)timestamp {
  1028. FFLog(@"I-RDB034041", @"Handling timestamp: %@", timestamp);
  1029. double timestampDeltaMs = [timestamp doubleValue] -
  1030. ([[NSDate date] timeIntervalSince1970] * 1000);
  1031. [self.delegate onServerInfoUpdate:self
  1032. updates:@{
  1033. kDotInfoServerTimeOffset : [NSNumber
  1034. numberWithDouble:timestampDeltaMs]
  1035. }];
  1036. }
  1037. - (void)sendStats:(NSDictionary *)stats {
  1038. if ([stats count] > 0) {
  1039. NSDictionary *request = @{kFWPRequestCounters : stats};
  1040. [self sendAction:kFWPRequestActionStats
  1041. body:request
  1042. sensitive:NO
  1043. callback:^(NSDictionary *data) {
  1044. NSString *status =
  1045. [data objectForKey:kFWPResponseForActionStatus];
  1046. NSString *errorReason =
  1047. [data objectForKey:kFWPResponseForActionData];
  1048. BOOL statusOk =
  1049. [status isEqualToString:kFWPResponseForActionStatusOk];
  1050. if (!statusOk) {
  1051. FFLog(@"I-RDB034042", @"Failed to send stats: %@",
  1052. errorReason);
  1053. }
  1054. }];
  1055. } else {
  1056. FFLog(@"I-RDB034043", @"Not sending stats because stats are empty");
  1057. }
  1058. }
  1059. - (void)sendConnectStats {
  1060. NSMutableDictionary *stats = [NSMutableDictionary dictionary];
  1061. #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
  1062. if (self.config.persistenceEnabled) {
  1063. stats[@"persistence.ios.enabled"] = @1;
  1064. }
  1065. #elif TARGET_OS_OSX
  1066. if (self.config.persistenceEnabled) {
  1067. stats[@"persistence.osx.enabled"] = @1;
  1068. }
  1069. #elif TARGET_OS_WATCH
  1070. if (self.config.persistenceEnabled) {
  1071. stats[@"persistence.watchos.enabled"] = @1;
  1072. }
  1073. #endif
  1074. NSString *sdkVersion =
  1075. [[FIRDatabase sdkVersion] stringByReplacingOccurrencesOfString:@"."
  1076. withString:@"-"];
  1077. NSString *sdkStatName =
  1078. [NSString stringWithFormat:@"sdk.objc.%@", sdkVersion];
  1079. stats[sdkStatName] = @1;
  1080. FFLog(@"I-RDB034044", @"Sending first connection stats");
  1081. [self sendStats:stats];
  1082. }
  1083. - (NSDictionary *)dumpListens {
  1084. return self.listens;
  1085. }
  1086. #pragma mark - App Check Token update
  1087. // TODO: Add tests!
  1088. - (void)refreshAppCheckToken:(NSString *)token {
  1089. if (![self connected]) {
  1090. // A fresh FAC token will be sent as a part of initial handshake.
  1091. return;
  1092. }
  1093. if (token.length == 0) {
  1094. // No token to send.
  1095. return;
  1096. }
  1097. // Send updated FAC token to the open connection.
  1098. [self sendAppCheckToken:token];
  1099. }
  1100. - (void)sendAppCheckToken:(NSString *)token {
  1101. NSDictionary *requestData = @{kFWPRequestAppCheckToken : token};
  1102. [self sendAction:kFWPRequestActionAppCheck
  1103. body:requestData
  1104. sensitive:YES
  1105. callback:^(NSDictionary *data) {
  1106. NSString *status =
  1107. [data objectForKey:kFWPResponseForActionStatus];
  1108. id responseData = [data objectForKey:kFWPResponseForActionData];
  1109. if (responseData == nil) {
  1110. responseData = @"Response data was empty.";
  1111. }
  1112. BOOL statusOk =
  1113. [status isEqualToString:kFWPResponseForActionStatusOk];
  1114. if (!statusOk) {
  1115. self.authToken = nil;
  1116. self.forceTokenRefreshes = YES;
  1117. if ([status isEqualToString:@"invalid_token"]) {
  1118. FFLog(@"I-RDB034045", @"App check failed: %@ (%@)",
  1119. status, responseData);
  1120. } else {
  1121. FFWarn(@"I-RDB034046", @"App check failed: %@ (%@)",
  1122. status, responseData);
  1123. }
  1124. [self.realtime close];
  1125. }
  1126. }];
  1127. }
  1128. @end