FIRMessagingDataMessageManager.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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 "FIRMessagingDataMessageManager.h"
  17. #import "Protos/GtalkCore.pbobjc.h"
  18. #import "FIRMessagingClient.h"
  19. #import "FIRMessagingConnection.h"
  20. #import "FIRMessagingConstants.h"
  21. #import "FIRMessagingDefines.h"
  22. #import "FIRMessagingDelayedMessageQueue.h"
  23. #import "FIRMessagingLogger.h"
  24. #import "FIRMessagingReceiver.h"
  25. #import "FIRMessagingRmqManager.h"
  26. #import "FIRMessaging_Private.h"
  27. #import "FIRMessagingSyncMessageManager.h"
  28. #import "FIRMessagingUtilities.h"
  29. #import "NSError+FIRMessaging.h"
  30. // The Notification used to send InstanceID messages that FIRMessaging receives.
  31. static NSString *const NOTIFICATION_IID_MESSAGE = @"com.google.gcm/notification/iid";
  32. static const int kMaxAppDataSizeDefault = 4 * 1024; // 4k
  33. static const int kMinDelaySeconds = 1; // 1 second
  34. static const int kMaxDelaySeconds = 60 * 60; // 1 hour
  35. static NSString *const kFromForInstanceIDMessages = @"google.com/iid";
  36. static NSString *const kFromForFIRMessagingMessages = @"mcs.android.com";
  37. static NSString *const kGSFMessageCategory = @"com.google.android.gsf.gtalkservice";
  38. // TODO: Update Gcm to FIRMessaging in the constants below
  39. static NSString *const kFCMMessageCategory = @"com.google.gcm";
  40. static NSString *const kMessageReservedPrefix = @"google.";
  41. static NSString *const kFCMMessageSpecialMessage = @"message_type";
  42. // special messages sent by the server
  43. static NSString *const kFCMMessageTypeDeletedMessages = @"deleted_messages";
  44. static NSString *const kMCSNotificationPrefix = @"gcm.notification.";
  45. static NSString *const kDataMessageNotificationKey = @"notification";
  46. typedef NS_ENUM(int8_t, UpstreamForceReconnect) {
  47. // Never force reconnect on upstream messages
  48. kUpstreamForceReconnectOff = 0,
  49. // Force reconnect for TTL=0 upstream messages
  50. kUpstreamForceReconnectTTL0 = 1,
  51. // Force reconnect for all upstream messages
  52. kUpstreamForceReconnectAll = 2,
  53. };
  54. @interface FIRMessagingDataMessageManager ()
  55. @property(nonatomic, readwrite, weak) FIRMessagingClient *client;
  56. @property(nonatomic, readwrite, weak) FIRMessagingRmqManager *rmq2Manager;
  57. @property(nonatomic, readwrite, weak) FIRMessagingSyncMessageManager *syncMessageManager;
  58. @property(nonatomic, readwrite, weak) id<FIRMessagingDataMessageManagerDelegate> delegate;
  59. @property(nonatomic, readwrite, strong) FIRMessagingDelayedMessageQueue *delayedMessagesQueue;
  60. @property(nonatomic, readwrite, assign) int ttl;
  61. @property(nonatomic, readwrite, copy) NSString *deviceAuthID;
  62. @property(nonatomic, readwrite, copy) NSString *secretToken;
  63. @property(nonatomic, readwrite, assign) int maxAppDataSize;
  64. @property(nonatomic, readwrite, assign) UpstreamForceReconnect upstreamForceReconnect;
  65. @end
  66. @implementation FIRMessagingDataMessageManager
  67. - (instancetype)initWithDelegate:(id<FIRMessagingDataMessageManagerDelegate>)delegate
  68. client:(FIRMessagingClient *)client
  69. rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager
  70. syncMessageManager:(FIRMessagingSyncMessageManager *)syncMessageManager {
  71. self = [super init];
  72. if (self) {
  73. _delegate = delegate;
  74. _client = client;
  75. _rmq2Manager = rmq2Manager;
  76. _syncMessageManager = syncMessageManager;
  77. _ttl = kFIRMessagingSendTtlDefault;
  78. _maxAppDataSize = kMaxAppDataSizeDefault;
  79. // on by default
  80. _upstreamForceReconnect = kUpstreamForceReconnectAll;
  81. }
  82. return self;
  83. }
  84. - (void)setDeviceAuthID:(NSString *)deviceAuthID secretToken:(NSString *)secretToken {
  85. _FIRMessagingDevAssert([deviceAuthID length] && [secretToken length],
  86. @"Invalid credentials for FIRMessaging");
  87. self.deviceAuthID = deviceAuthID;
  88. self.secretToken = secretToken;
  89. }
  90. - (void)refreshDelayedMessages {
  91. FIRMessaging_WEAKIFY(self);
  92. self.delayedMessagesQueue =
  93. [[FIRMessagingDelayedMessageQueue alloc] initWithRmqScanner:self.rmq2Manager
  94. sendDelayedMessagesHandler:^(NSArray *messages) {
  95. FIRMessaging_STRONGIFY(self);
  96. [self sendDelayedMessages:messages];
  97. }];
  98. }
  99. - (NSDictionary *)processPacket:(GtalkDataMessageStanza *)dataMessage {
  100. NSString *category = dataMessage.category;
  101. NSString *from = dataMessage.from;
  102. if ([kFCMMessageCategory isEqualToString:category] ||
  103. [kGSFMessageCategory isEqualToString:category]) {
  104. [self handleMCSDataMessage:dataMessage];
  105. return nil;
  106. } else if ([kFromForFIRMessagingMessages isEqualToString:from]) {
  107. [self handleMCSDataMessage:dataMessage];
  108. return nil;
  109. } else if ([kFromForInstanceIDMessages isEqualToString:from]) {
  110. // send message to InstanceID library.
  111. NSMutableDictionary *message = [NSMutableDictionary dictionary];
  112. for (GtalkAppData *item in dataMessage.appDataArray) {
  113. _FIRMessagingDevAssert(item.key && item.value, @"Invalid app data item");
  114. if (item.key && item.value) {
  115. message[item.key] = item.value;
  116. }
  117. }
  118. [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_IID_MESSAGE
  119. object:message];
  120. return nil;
  121. }
  122. return [self parseDataMessage:dataMessage];
  123. }
  124. - (void)handleMCSDataMessage:(GtalkDataMessageStanza *)dataMessage {
  125. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager000,
  126. @"Received message for FIRMessaging from downstream %@", dataMessage);
  127. }
  128. - (NSDictionary *)parseDataMessage:(GtalkDataMessageStanza *)dataMessage {
  129. NSMutableDictionary *message = [NSMutableDictionary dictionary];
  130. NSString *from = [dataMessage from];
  131. if ([from length]) {
  132. message[kFIRMessagingFromKey] = from;
  133. }
  134. // raw data
  135. NSData *rawData = [dataMessage rawData];
  136. if ([rawData length]) {
  137. message[kFIRMessagingRawDataKey] = rawData;
  138. }
  139. NSString *token = [dataMessage token];
  140. if ([token length]) {
  141. message[kFIRMessagingCollapseKey] = token;
  142. }
  143. // Add the persistent_id. This would be removed later before sending the message to the device.
  144. NSString *persistentID = [dataMessage persistentId];
  145. _FIRMessagingDevAssert([persistentID length], @"Invalid MCS message without persistentID");
  146. if ([persistentID length]) {
  147. message[kFIRMessagingMessageIDKey] = persistentID;
  148. }
  149. // third-party data
  150. for (GtalkAppData *item in dataMessage.appDataArray) {
  151. _FIRMessagingDevAssert(item.hasKey && item.hasValue, @"Invalid AppData");
  152. // do not process the "from" key -- is not useful
  153. if ([kFIRMessagingFromKey isEqualToString:item.key]) {
  154. continue;
  155. }
  156. // Filter the "gcm.notification." keys in the message
  157. if ([item.key hasPrefix:kMCSNotificationPrefix]) {
  158. NSString *key = [item.key substringFromIndex:[kMCSNotificationPrefix length]];
  159. if ([key length]) {
  160. if (!message[kDataMessageNotificationKey]) {
  161. message[kDataMessageNotificationKey] = [NSMutableDictionary dictionary];
  162. }
  163. message[kDataMessageNotificationKey][key] = item.value;
  164. } else {
  165. _FIRMessagingDevAssert([key length], @"Invalid key in MCS message: %@", key);
  166. FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager001,
  167. @"Invalid key in MCS message: %@", key);
  168. }
  169. continue;
  170. }
  171. // Filter the "gcm.duplex" key
  172. if ([item.key isEqualToString:kFIRMessagingMessageSyncViaMCSKey]) {
  173. BOOL value = [item.value boolValue];
  174. message[kFIRMessagingMessageSyncViaMCSKey] = @(value);
  175. continue;
  176. }
  177. // do not allow keys with "reserved" keyword
  178. if ([[item.key lowercaseString] hasPrefix:kMessageReservedPrefix]) {
  179. continue;
  180. }
  181. [message setObject:item.value forKey:item.key];
  182. }
  183. // TODO: Add support for encrypting raw data later
  184. return [NSDictionary dictionaryWithDictionary:message];
  185. }
  186. - (void)didReceiveParsedMessage:(NSDictionary *)message {
  187. if ([message[kFCMMessageSpecialMessage] length]) {
  188. NSString *messageType = message[kFCMMessageSpecialMessage];
  189. if ([kFCMMessageTypeDeletedMessages isEqualToString:messageType]) {
  190. // TODO: Maybe trim down message to remove some unnecessary fields.
  191. // tell the FCM receiver of deleted messages
  192. [self.delegate didDeleteMessagesOnServer];
  193. return;
  194. }
  195. FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager002,
  196. @"Invalid message type received: %@", messageType);
  197. } else if (message[kFIRMessagingMessageSyncViaMCSKey]) {
  198. // Update SYNC_RMQ with the message
  199. BOOL isDuplicate = [self.syncMessageManager didReceiveMCSSyncMessage:message];
  200. if (isDuplicate) {
  201. return;
  202. }
  203. }
  204. NSString *messageId = message[kFIRMessagingMessageIDKey];
  205. NSDictionary *filteredMessage = [self filterInternalFIRMessagingKeysFromMessage:message];
  206. [self.delegate didReceiveMessage:filteredMessage withIdentifier:messageId];
  207. }
  208. - (NSDictionary *)filterInternalFIRMessagingKeysFromMessage:(NSDictionary *)message {
  209. NSMutableDictionary *newMessage = [NSMutableDictionary dictionaryWithDictionary:message];
  210. for (NSString *key in message) {
  211. if ([key hasPrefix:kFIRMessagingMessageInternalReservedKeyword]) {
  212. [newMessage removeObjectForKey:key];
  213. }
  214. }
  215. return [newMessage copy];
  216. }
  217. - (void)sendDataMessageStanza:(NSMutableDictionary *)dataMessage {
  218. NSNumber *ttlNumber = dataMessage[kFIRMessagingSendTTL];
  219. NSString *to = dataMessage[kFIRMessagingSendTo];
  220. NSString *msgId = dataMessage[kFIRMessagingSendMessageID];
  221. NSString *appPackage = [self categoryForUpstreamMessages];
  222. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  223. // TODO: enforce TTL (right now only ttl=0 is special, means no storage)
  224. int ttl = [ttlNumber intValue];
  225. if (ttl < 0 || ttl > self.ttl) {
  226. ttl = self.ttl;
  227. }
  228. [stanza setTtl:ttl];
  229. [stanza setSent:FIRMessagingCurrentTimestampInSeconds()];
  230. int delay = [self delayForMessage:dataMessage];
  231. if (delay > 0) {
  232. [stanza setMaxDelay:delay];
  233. }
  234. if (msgId) {
  235. [stanza setId_p:msgId];
  236. }
  237. // collapse key as given by the sender
  238. NSString *token = dataMessage[KFIRMessagingSendMessageAppData][kFIRMessagingCollapseKey];
  239. if ([token length]) {
  240. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager003,
  241. @"FIRMessaging using %@ as collapse key", token);
  242. [stanza setToken:token];
  243. }
  244. if (!self.secretToken) {
  245. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager004,
  246. @"Trying to send data message without a secret token. "
  247. @"Authentication failed.");
  248. [self willSendDataMessageFail:stanza
  249. withMessageId:msgId
  250. error:kFIRMessagingErrorCodeMissingDeviceID];
  251. return;
  252. }
  253. if (![to length]) {
  254. [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorMissingTo];
  255. return;
  256. }
  257. [stanza setTo:to];
  258. [stanza setCategory:appPackage];
  259. // required field in the proto this is set by the server
  260. // set it to a sentinel so the runtime doesn't throw an exception
  261. [stanza setFrom:@""];
  262. // MCS itself would set the registration ID
  263. // [stanza setRegId:nil];
  264. int size = [self addData:dataMessage[KFIRMessagingSendMessageAppData] toStanza:stanza];
  265. if (size > kMaxAppDataSizeDefault) {
  266. [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorSizeExceeded];
  267. return;
  268. }
  269. BOOL useRmq = (ttl != 0) && (msgId != nil);
  270. if (useRmq) {
  271. if (!self.client.isConnected) {
  272. // do nothing assuming rmq save is enabled
  273. }
  274. NSError *error;
  275. if (![self.rmq2Manager saveRmqMessage:stanza error:&error]) {
  276. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager005, @"%@", error);
  277. [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorSave];
  278. return;
  279. }
  280. [self willSendDataMessageSuccess:stanza withMessageId:msgId];
  281. }
  282. // if delay > 0 we don't really care about sending the message right now
  283. // so we piggy-back on any other urgent(delay = 0) message that we are sending
  284. if (delay > 0 && [self delayMessage:stanza]) {
  285. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager006, @"Delaying Message %@",
  286. dataMessage);
  287. return;
  288. }
  289. // send delayed messages
  290. [self sendDelayedMessages:[self.delayedMessagesQueue removeDelayedMessages]];
  291. BOOL sending = [self tryToSendDataMessageStanza:stanza];
  292. if (!sending) {
  293. if (useRmq) {
  294. NSString *event __unused = [NSString stringWithFormat:@"Queued message: %@", [stanza id_p]];
  295. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager007, @"%@", event);
  296. } else {
  297. [self willSendDataMessageFail:stanza
  298. withMessageId:msgId
  299. error:kFIRMessagingErrorCodeNetwork];
  300. return;
  301. }
  302. }
  303. }
  304. - (void)sendDelayedMessages:(NSArray *)delayedMessages {
  305. for (GtalkDataMessageStanza *message in delayedMessages) {
  306. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager008,
  307. @"%@ Sending delayed message %@", @"DMM", message);
  308. [message setActualDelay:(int)(FIRMessagingCurrentTimestampInSeconds() - message.sent)];
  309. [self tryToSendDataMessageStanza:message];
  310. }
  311. }
  312. - (void)didSendDataMessageStanza:(GtalkDataMessageStanza *)message {
  313. NSString *msgId = [message id_p] ?: @"";
  314. [self.delegate didSendDataMessageWithID:msgId];
  315. }
  316. - (void)addParamWithKey:(NSString *)key
  317. value:(NSString *)val
  318. toStanza:(GtalkDataMessageStanza *)stanza {
  319. if (!key || !val) {
  320. return;
  321. }
  322. GtalkAppData *appData = [[GtalkAppData alloc] init];
  323. [appData setKey:key];
  324. [appData setValue:val];
  325. [[stanza appDataArray] addObject:appData];
  326. }
  327. /**
  328. @return The size of the data being added to stanza.
  329. */
  330. - (int)addData:(NSDictionary *)data toStanza:(GtalkDataMessageStanza *)stanza {
  331. int size = 0;
  332. for (NSString *key in data) {
  333. NSObject *val = data[key];
  334. if ([val isKindOfClass:[NSString class]]) {
  335. NSString *strVal = (NSString *)val;
  336. [self addParamWithKey:key value:strVal toStanza:stanza];
  337. size += [key length] + [strVal length];
  338. } else if ([val isKindOfClass:[NSNumber class]]) {
  339. NSString *strVal = [(NSNumber *)val stringValue];
  340. [self addParamWithKey:key value:strVal toStanza:stanza];
  341. size += [key length] + [strVal length];
  342. } else if ([kFIRMessagingRawDataKey isEqualToString:key] &&
  343. [val isKindOfClass:[NSData class]]) {
  344. NSData *rawData = (NSData *)val;
  345. [stanza setRawData:[rawData copy]];
  346. size += [rawData length];
  347. } else {
  348. FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager009, @"Ignoring key: %@",
  349. key);
  350. }
  351. }
  352. return size;
  353. }
  354. /**
  355. * Notify the messenger that send data message completed with success. This is called for
  356. * TTL=0, after the message has been sent, or when message is saved, to unlock the send()
  357. * method.
  358. */
  359. - (void)willSendDataMessageSuccess:(GtalkDataMessageStanza *)stanza
  360. withMessageId:(NSString *)messageId {
  361. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager010,
  362. @"send message success: %@", messageId);
  363. [self.delegate willSendDataMessageWithID:messageId error:nil];
  364. }
  365. /**
  366. * We send 'send failures' from server as normal FIRMessaging messages, with a 'message_type'
  367. * extra - same as 'message deleted'.
  368. *
  369. * For TTL=0 or errors that can be detected during send ( too many messages, invalid, etc)
  370. * we throw IOExceptions
  371. */
  372. - (void)willSendDataMessageFail:(GtalkDataMessageStanza *)stanza
  373. withMessageId:(NSString *)messageId
  374. error:(FIRMessagingInternalErrorCode)errorCode {
  375. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager011,
  376. @"Send message fail: %@ error: %lu", messageId, (unsigned long)errorCode);
  377. NSError *error = [NSError errorWithFCMErrorCode:errorCode];
  378. if ([self.delegate respondsToSelector:@selector(willSendDataMessageWithID:error:)]) {
  379. [self.delegate willSendDataMessageWithID:messageId error:error];
  380. }
  381. }
  382. - (void)resendMessagesWithConnection:(FIRMessagingConnection *)connection {
  383. NSMutableString *rmqIdsResent = [NSMutableString string];
  384. NSMutableArray *toRemoveRmqIds = [NSMutableArray array];
  385. FIRMessaging_WEAKIFY(self);
  386. FIRMessaging_WEAKIFY(connection);
  387. FIRMessagingRmqMessageHandler messageHandler = ^(int64_t rmqId, int8_t tag, NSData *data) {
  388. FIRMessaging_STRONGIFY(self);
  389. FIRMessaging_STRONGIFY(connection);
  390. GPBMessage *proto =
  391. [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data error:NULL];
  392. if ([proto isKindOfClass:GtalkDataMessageStanza.class]) {
  393. GtalkDataMessageStanza *stanza = (GtalkDataMessageStanza *)proto;
  394. if (![self handleExpirationForDataMessage:stanza]) {
  395. // time expired let's delete from RMQ
  396. [toRemoveRmqIds addObject:stanza.persistentId];
  397. return;
  398. }
  399. [rmqIdsResent appendString:[NSString stringWithFormat:@"%@,", stanza.id_p]];
  400. }
  401. [connection sendProto:proto];
  402. };
  403. [self.rmq2Manager scanWithRmqMessageHandler:messageHandler
  404. dataMessageHandler:nil];
  405. if ([rmqIdsResent length]) {
  406. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager012, @"Resent: %@",
  407. rmqIdsResent);
  408. }
  409. if ([toRemoveRmqIds count]) {
  410. [self.rmq2Manager removeRmqMessagesWithRmqIds:toRemoveRmqIds];
  411. }
  412. }
  413. /**
  414. * Check the TTL and generate an error if needed.
  415. *
  416. * @return false if the message needs to be deleted
  417. */
  418. - (BOOL)handleExpirationForDataMessage:(GtalkDataMessageStanza *)message {
  419. if (message.ttl == 0) {
  420. return NO;
  421. }
  422. int64_t now = FIRMessagingCurrentTimestampInSeconds();
  423. if (now > message.sent + message.ttl) {
  424. [self willSendDataMessageFail:message
  425. withMessageId:message.id_p
  426. error:kFIRMessagingErrorServiceNotAvailable];
  427. return NO;
  428. }
  429. return YES;
  430. }
  431. #pragma mark - Private
  432. - (int)delayForMessage:(NSMutableDictionary *)message {
  433. int delay = 0; // default
  434. if (message[kFIRMessagingSendDelay]) {
  435. delay = [message[kFIRMessagingSendDelay] intValue];
  436. [message removeObjectForKey:kFIRMessagingSendDelay];
  437. if (delay < kMinDelaySeconds) {
  438. delay = 0;
  439. } else if (delay > kMaxDelaySeconds) {
  440. delay = kMaxDelaySeconds;
  441. }
  442. }
  443. return delay;
  444. }
  445. // return True if successfully delayed else False
  446. - (BOOL)delayMessage:(GtalkDataMessageStanza *)message {
  447. return [self.delayedMessagesQueue queueMessage:message];
  448. }
  449. - (BOOL)tryToSendDataMessageStanza:(GtalkDataMessageStanza *)stanza {
  450. if (self.client.isConnectionActive) {
  451. [self.client sendMessage:stanza];
  452. return YES;
  453. }
  454. // if we only reconnect for TTL = 0 messages check if we ttl = 0 or
  455. // if we reconnect for all messages try to reconnect
  456. if ((self.upstreamForceReconnect == kUpstreamForceReconnectTTL0 && stanza.ttl == 0) ||
  457. self.upstreamForceReconnect == kUpstreamForceReconnectAll) {
  458. BOOL isNetworkAvailable = [[FIRMessaging messaging] isNetworkAvailable];
  459. if (isNetworkAvailable) {
  460. if (stanza.ttl == 0) {
  461. // Add TTL = 0 messages to be sent on next connect. TTL != 0 messages are
  462. // persisted, and will be sent from the RMQ.
  463. [self.client sendOnConnectOrDrop:stanza];
  464. }
  465. [self.client retryConnectionImmediately:YES];
  466. return YES;
  467. }
  468. }
  469. return NO;
  470. }
  471. - (NSString *)categoryForUpstreamMessages {
  472. return FIRMessagingAppIdentifier();
  473. }
  474. @end