X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/FirebaseMessaging/Firebase/Messaging/FIRMessagingDataMessageManager.m diff --git a/iOS/Pods/FirebaseMessaging/Firebase/Messaging/FIRMessagingDataMessageManager.m b/iOS/Pods/FirebaseMessaging/Firebase/Messaging/FIRMessagingDataMessageManager.m new file mode 100644 index 0000000..b62763a --- /dev/null +++ b/iOS/Pods/FirebaseMessaging/Firebase/Messaging/FIRMessagingDataMessageManager.m @@ -0,0 +1,528 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRMessagingDataMessageManager.h" + +#import "Protos/GtalkCore.pbobjc.h" + +#import "FIRMessagingClient.h" +#import "FIRMessagingConnection.h" +#import "FIRMessagingConstants.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingDelayedMessageQueue.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingReceiver.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessaging_Private.h" +#import "FIRMessagingSyncMessageManager.h" +#import "FIRMessagingUtilities.h" +#import "NSError+FIRMessaging.h" + +static const int kMaxAppDataSizeDefault = 4 * 1024; // 4k +static const int kMinDelaySeconds = 1; // 1 second +static const int kMaxDelaySeconds = 60 * 60; // 1 hour + +static NSString *const kFromForFIRMessagingMessages = @"mcs.android.com"; +static NSString *const kGSFMessageCategory = @"com.google.android.gsf.gtalkservice"; +// TODO: Update Gcm to FIRMessaging in the constants below +static NSString *const kFCMMessageCategory = @"com.google.gcm"; +static NSString *const kMessageReservedPrefix = @"google."; + +static NSString *const kFCMMessageSpecialMessage = @"message_type"; + +// special messages sent by the server +static NSString *const kFCMMessageTypeDeletedMessages = @"deleted_messages"; + +static NSString *const kMCSNotificationPrefix = @"gcm.notification."; +static NSString *const kDataMessageNotificationKey = @"notification"; + + +typedef NS_ENUM(int8_t, UpstreamForceReconnect) { + // Never force reconnect on upstream messages + kUpstreamForceReconnectOff = 0, + // Force reconnect for TTL=0 upstream messages + kUpstreamForceReconnectTTL0 = 1, + // Force reconnect for all upstream messages + kUpstreamForceReconnectAll = 2, +}; + +@interface FIRMessagingDataMessageManager () + +@property(nonatomic, readwrite, weak) FIRMessagingClient *client; +@property(nonatomic, readwrite, weak) FIRMessagingRmqManager *rmq2Manager; +@property(nonatomic, readwrite, weak) FIRMessagingSyncMessageManager *syncMessageManager; +@property(nonatomic, readwrite, weak) id delegate; +@property(nonatomic, readwrite, strong) FIRMessagingDelayedMessageQueue *delayedMessagesQueue; + +@property(nonatomic, readwrite, assign) int ttl; +@property(nonatomic, readwrite, copy) NSString *deviceAuthID; +@property(nonatomic, readwrite, copy) NSString *secretToken; +@property(nonatomic, readwrite, assign) int maxAppDataSize; +@property(nonatomic, readwrite, assign) UpstreamForceReconnect upstreamForceReconnect; + +@end + +@implementation FIRMessagingDataMessageManager + +- (instancetype)initWithDelegate:(id)delegate + client:(FIRMessagingClient *)client + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager + syncMessageManager:(FIRMessagingSyncMessageManager *)syncMessageManager { + self = [super init]; + if (self) { + _delegate = delegate; + _client = client; + _rmq2Manager = rmq2Manager; + _syncMessageManager = syncMessageManager; + _ttl = kFIRMessagingSendTtlDefault; + _maxAppDataSize = kMaxAppDataSizeDefault; + // on by default + _upstreamForceReconnect = kUpstreamForceReconnectAll; + } + return self; +} + +- (void)setDeviceAuthID:(NSString *)deviceAuthID secretToken:(NSString *)secretToken { + _FIRMessagingDevAssert([deviceAuthID length] && [secretToken length], + @"Invalid credentials for FIRMessaging"); + self.deviceAuthID = deviceAuthID; + self.secretToken = secretToken; +} + +- (void)refreshDelayedMessages { + FIRMessaging_WEAKIFY(self); + self.delayedMessagesQueue = + [[FIRMessagingDelayedMessageQueue alloc] initWithRmqScanner:self.rmq2Manager + sendDelayedMessagesHandler:^(NSArray *messages) { + FIRMessaging_STRONGIFY(self); + [self sendDelayedMessages:messages]; + }]; +} + +- (nullable NSDictionary *)processPacket:(GtalkDataMessageStanza *)dataMessage { + NSString *category = dataMessage.category; + NSString *from = dataMessage.from; + if ([kFCMMessageCategory isEqualToString:category] || + [kGSFMessageCategory isEqualToString:category]) { + [self handleMCSDataMessage:dataMessage]; + return nil; + } else if ([kFromForFIRMessagingMessages isEqualToString:from]) { + [self handleMCSDataMessage:dataMessage]; + return nil; + } + + return [self parseDataMessage:dataMessage]; +} + +- (void)handleMCSDataMessage:(GtalkDataMessageStanza *)dataMessage { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager000, + @"Received message for FIRMessaging from downstream %@", dataMessage); +} + +- (NSDictionary *)parseDataMessage:(GtalkDataMessageStanza *)dataMessage { + NSMutableDictionary *message = [NSMutableDictionary dictionary]; + NSString *from = [dataMessage from]; + if ([from length]) { + message[kFIRMessagingFromKey] = from; + } + + // raw data + NSData *rawData = [dataMessage rawData]; + if ([rawData length]) { + message[kFIRMessagingRawDataKey] = rawData; + } + + NSString *token = [dataMessage token]; + if ([token length]) { + message[kFIRMessagingCollapseKey] = token; + } + + // Add the persistent_id. This would be removed later before sending the message to the device. + NSString *persistentID = [dataMessage persistentId]; + _FIRMessagingDevAssert([persistentID length], @"Invalid MCS message without persistentID"); + if ([persistentID length]) { + message[kFIRMessagingMessageIDKey] = persistentID; + } + + // third-party data + for (GtalkAppData *item in dataMessage.appDataArray) { + _FIRMessagingDevAssert(item.hasKey && item.hasValue, @"Invalid AppData"); + + // do not process the "from" key -- is not useful + if ([kFIRMessagingFromKey isEqualToString:item.key]) { + continue; + } + + // Filter the "gcm.notification." keys in the message + if ([item.key hasPrefix:kMCSNotificationPrefix]) { + NSString *key = [item.key substringFromIndex:[kMCSNotificationPrefix length]]; + if ([key length]) { + if (!message[kDataMessageNotificationKey]) { + message[kDataMessageNotificationKey] = [NSMutableDictionary dictionary]; + } + message[kDataMessageNotificationKey][key] = item.value; + } else { + _FIRMessagingDevAssert([key length], @"Invalid key in MCS message: %@", key); + FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager001, + @"Invalid key in MCS message: %@", key); + } + continue; + } + + // Filter the "gcm.duplex" key + if ([item.key isEqualToString:kFIRMessagingMessageSyncViaMCSKey]) { + BOOL value = [item.value boolValue]; + message[kFIRMessagingMessageSyncViaMCSKey] = @(value); + continue; + } + + // do not allow keys with "reserved" keyword + if ([[item.key lowercaseString] hasPrefix:kMessageReservedPrefix]) { + continue; + } + + [message setObject:item.value forKey:item.key]; + } + // TODO: Add support for encrypting raw data later + return [NSDictionary dictionaryWithDictionary:message]; +} + +- (void)didReceiveParsedMessage:(NSDictionary *)message { + if ([message[kFCMMessageSpecialMessage] length]) { + NSString *messageType = message[kFCMMessageSpecialMessage]; + if ([kFCMMessageTypeDeletedMessages isEqualToString:messageType]) { + // TODO: Maybe trim down message to remove some unnecessary fields. + // tell the FCM receiver of deleted messages + [self.delegate didDeleteMessagesOnServer]; + return; + } + FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager002, + @"Invalid message type received: %@", messageType); + } else if (message[kFIRMessagingMessageSyncViaMCSKey]) { + // Update SYNC_RMQ with the message + BOOL isDuplicate = [self.syncMessageManager didReceiveMCSSyncMessage:message]; + if (isDuplicate) { + return; + } + } + NSString *messageId = message[kFIRMessagingMessageIDKey]; + NSDictionary *filteredMessage = [self filterInternalFIRMessagingKeysFromMessage:message]; + [self.delegate didReceiveMessage:filteredMessage withIdentifier:messageId]; +} + +- (NSDictionary *)filterInternalFIRMessagingKeysFromMessage:(NSDictionary *)message { + NSMutableDictionary *newMessage = [NSMutableDictionary dictionaryWithDictionary:message]; + for (NSString *key in message) { + if ([key hasPrefix:kFIRMessagingMessageInternalReservedKeyword]) { + [newMessage removeObjectForKey:key]; + } + } + return [newMessage copy]; +} + +- (void)sendDataMessageStanza:(NSMutableDictionary *)dataMessage { + NSNumber *ttlNumber = dataMessage[kFIRMessagingSendTTL]; + NSString *to = dataMessage[kFIRMessagingSendTo]; + NSString *msgId = dataMessage[kFIRMessagingSendMessageID]; + NSString *appPackage = [self categoryForUpstreamMessages]; + GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init]; + + // TODO: enforce TTL (right now only ttl=0 is special, means no storage) + int ttl = [ttlNumber intValue]; + if (ttl < 0 || ttl > self.ttl) { + ttl = self.ttl; + } + [stanza setTtl:ttl]; + [stanza setSent:FIRMessagingCurrentTimestampInSeconds()]; + + int delay = [self delayForMessage:dataMessage]; + if (delay > 0) { + [stanza setMaxDelay:delay]; + } + + if (msgId) { + [stanza setId_p:msgId]; + } + + // collapse key as given by the sender + NSString *token = dataMessage[KFIRMessagingSendMessageAppData][kFIRMessagingCollapseKey]; + if ([token length]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager003, + @"FIRMessaging using %@ as collapse key", token); + [stanza setToken:token]; + } + + if (!self.secretToken) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager004, + @"Trying to send data message without a secret token. " + @"Authentication failed."); + [self willSendDataMessageFail:stanza + withMessageId:msgId + error:kFIRMessagingErrorCodeMissingDeviceID]; + return; + } + + if (![to length]) { + [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorMissingTo]; + return; + } + [stanza setTo:to]; + [stanza setCategory:appPackage]; + // required field in the proto this is set by the server + // set it to a sentinel so the runtime doesn't throw an exception + [stanza setFrom:@""]; + + // MCS itself would set the registration ID + // [stanza setRegId:nil]; + + int size = [self addData:dataMessage[KFIRMessagingSendMessageAppData] toStanza:stanza]; + if (size > kMaxAppDataSizeDefault) { + [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorSizeExceeded]; + return; + } + + BOOL useRmq = (ttl != 0) && (msgId != nil); + if (useRmq) { + if (!self.client.isConnected) { + // do nothing assuming rmq save is enabled + } + + NSError *error; + if (![self.rmq2Manager saveRmqMessage:stanza error:&error]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager005, @"%@", error); + [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorSave]; + return; + } + + [self willSendDataMessageSuccess:stanza withMessageId:msgId]; + } + + // if delay > 0 we don't really care about sending the message right now + // so we piggy-back on any other urgent(delay = 0) message that we are sending + if (delay > 0 && [self delayMessage:stanza]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager006, @"Delaying Message %@", + dataMessage); + return; + } + // send delayed messages + [self sendDelayedMessages:[self.delayedMessagesQueue removeDelayedMessages]]; + + BOOL sending = [self tryToSendDataMessageStanza:stanza]; + if (!sending) { + if (useRmq) { + NSString *event __unused = [NSString stringWithFormat:@"Queued message: %@", [stanza id_p]]; + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager007, @"%@", event); + } else { + [self willSendDataMessageFail:stanza + withMessageId:msgId + error:kFIRMessagingErrorCodeNetwork]; + return; + } + } +} + +- (void)sendDelayedMessages:(NSArray *)delayedMessages { + for (GtalkDataMessageStanza *message in delayedMessages) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager008, + @"%@ Sending delayed message %@", @"DMM", message); + [message setActualDelay:(int)(FIRMessagingCurrentTimestampInSeconds() - message.sent)]; + [self tryToSendDataMessageStanza:message]; + } +} + +- (void)didSendDataMessageStanza:(GtalkDataMessageStanza *)message { + NSString *msgId = [message id_p] ?: @""; + [self.delegate didSendDataMessageWithID:msgId]; +} + +- (void)addParamWithKey:(NSString *)key + value:(NSString *)val + toStanza:(GtalkDataMessageStanza *)stanza { + if (!key || !val) { + return; + } + GtalkAppData *appData = [[GtalkAppData alloc] init]; + [appData setKey:key]; + [appData setValue:val]; + [[stanza appDataArray] addObject:appData]; +} + +/** + @return The size of the data being added to stanza. + */ +- (int)addData:(NSDictionary *)data toStanza:(GtalkDataMessageStanza *)stanza { + int size = 0; + for (NSString *key in data) { + NSObject *val = data[key]; + if ([val isKindOfClass:[NSString class]]) { + NSString *strVal = (NSString *)val; + [self addParamWithKey:key value:strVal toStanza:stanza]; + size += [key length] + [strVal length]; + } else if ([val isKindOfClass:[NSNumber class]]) { + NSString *strVal = [(NSNumber *)val stringValue]; + [self addParamWithKey:key value:strVal toStanza:stanza]; + size += [key length] + [strVal length]; + } else if ([kFIRMessagingRawDataKey isEqualToString:key] && + [val isKindOfClass:[NSData class]]) { + NSData *rawData = (NSData *)val; + [stanza setRawData:[rawData copy]]; + size += [rawData length]; + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager009, @"Ignoring key: %@", + key); + } + } + return size; +} + +/** + * Notify the messenger that send data message completed with success. This is called for + * TTL=0, after the message has been sent, or when message is saved, to unlock the send() + * method. + */ +- (void)willSendDataMessageSuccess:(GtalkDataMessageStanza *)stanza + withMessageId:(NSString *)messageId { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager010, + @"send message success: %@", messageId); + [self.delegate willSendDataMessageWithID:messageId error:nil]; +} + +/** + * We send 'send failures' from server as normal FIRMessaging messages, with a 'message_type' + * extra - same as 'message deleted'. + * + * For TTL=0 or errors that can be detected during send ( too many messages, invalid, etc) + * we throw IOExceptions + */ +- (void)willSendDataMessageFail:(GtalkDataMessageStanza *)stanza + withMessageId:(NSString *)messageId + error:(FIRMessagingInternalErrorCode)errorCode { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager011, + @"Send message fail: %@ error: %lu", messageId, (unsigned long)errorCode); + + NSError *error = [NSError errorWithFCMErrorCode:errorCode]; + if ([self.delegate respondsToSelector:@selector(willSendDataMessageWithID:error:)]) { + [self.delegate willSendDataMessageWithID:messageId error:error]; + } +} + +- (void)resendMessagesWithConnection:(FIRMessagingConnection *)connection { + NSMutableString *rmqIdsResent = [NSMutableString string]; + NSMutableArray *toRemoveRmqIds = [NSMutableArray array]; + FIRMessaging_WEAKIFY(self); + FIRMessaging_WEAKIFY(connection); + FIRMessagingRmqMessageHandler messageHandler = ^(int64_t rmqId, int8_t tag, NSData *data) { + FIRMessaging_STRONGIFY(self); + FIRMessaging_STRONGIFY(connection); + GPBMessage *proto = + [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data error:NULL]; + if ([proto isKindOfClass:GtalkDataMessageStanza.class]) { + GtalkDataMessageStanza *stanza = (GtalkDataMessageStanza *)proto; + + if (![self handleExpirationForDataMessage:stanza]) { + // time expired let's delete from RMQ + [toRemoveRmqIds addObject:stanza.persistentId]; + return; + } + [rmqIdsResent appendString:[NSString stringWithFormat:@"%@,", stanza.id_p]]; + } + + [connection sendProto:proto]; + }; + [self.rmq2Manager scanWithRmqMessageHandler:messageHandler + dataMessageHandler:nil]; + + if ([rmqIdsResent length]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager012, @"Resent: %@", + rmqIdsResent); + } + + if ([toRemoveRmqIds count]) { + [self.rmq2Manager removeRmqMessagesWithRmqIds:toRemoveRmqIds]; + } +} + +/** + * Check the TTL and generate an error if needed. + * + * @return false if the message needs to be deleted + */ +- (BOOL)handleExpirationForDataMessage:(GtalkDataMessageStanza *)message { + if (message.ttl == 0) { + return NO; + } + + int64_t now = FIRMessagingCurrentTimestampInSeconds(); + if (now > message.sent + message.ttl) { + [self willSendDataMessageFail:message + withMessageId:message.id_p + error:kFIRMessagingErrorServiceNotAvailable]; + return NO; + } + return YES; +} + +#pragma mark - Private + +- (int)delayForMessage:(NSMutableDictionary *)message { + int delay = 0; // default + if (message[kFIRMessagingSendDelay]) { + delay = [message[kFIRMessagingSendDelay] intValue]; + [message removeObjectForKey:kFIRMessagingSendDelay]; + if (delay < kMinDelaySeconds) { + delay = 0; + } else if (delay > kMaxDelaySeconds) { + delay = kMaxDelaySeconds; + } + } + return delay; +} + +// return True if successfully delayed else False +- (BOOL)delayMessage:(GtalkDataMessageStanza *)message { + return [self.delayedMessagesQueue queueMessage:message]; +} + +- (BOOL)tryToSendDataMessageStanza:(GtalkDataMessageStanza *)stanza { + if (self.client.isConnectionActive) { + [self.client sendMessage:stanza]; + return YES; + } + + // if we only reconnect for TTL = 0 messages check if we ttl = 0 or + // if we reconnect for all messages try to reconnect + if ((self.upstreamForceReconnect == kUpstreamForceReconnectTTL0 && stanza.ttl == 0) || + self.upstreamForceReconnect == kUpstreamForceReconnectAll) { + BOOL isNetworkAvailable = [[FIRMessaging messaging] isNetworkAvailable]; + if (isNetworkAvailable) { + if (stanza.ttl == 0) { + // Add TTL = 0 messages to be sent on next connect. TTL != 0 messages are + // persisted, and will be sent from the RMQ. + [self.client sendOnConnectOrDrop:stanza]; + } + + [self.client retryConnectionImmediately:YES]; + return YES; + } + } + return NO; +} + +- (NSString *)categoryForUpstreamMessages { + return FIRMessagingAppIdentifier(); +} + +@end