FIRMessagingExtensionHelper.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. * Copyright 2019 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 <nanopb/pb.h>
  17. #import <nanopb/pb_decode.h>
  18. #import <nanopb/pb_encode.h>
  19. #import <GoogleDataTransport/GoogleDataTransport.h>
  20. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  21. #import "FirebaseMessaging/Sources/FIRMessagingCode.h"
  22. #import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
  23. #import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
  24. #import "FirebaseMessaging/Sources/Protogen/nanopb/me.nanopb.h"
  25. #import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h"
  26. static NSString *const kPayloadOptionsName = @"fcm_options";
  27. static NSString *const kPayloadOptionsImageURLName = @"image";
  28. static NSString *const kNoExtension = @"";
  29. static NSString *const kImagePathPrefix = @"image/";
  30. #pragma mark - nanopb helper functions
  31. /** Callocs a pb_bytes_array and copies the given NSData bytes into the bytes array.
  32. *
  33. * @note Memory needs to be free manually, through pb_free or pb_release.
  34. * @param data The data to copy into the new bytes array.
  35. */
  36. pb_bytes_array_t *FIRMessagingEncodeData(NSData *data) {
  37. pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length));
  38. if (pbBytesArray != NULL) {
  39. [data getBytes:pbBytesArray->bytes length:data.length];
  40. pbBytesArray->size = (pb_size_t)data.length;
  41. }
  42. return pbBytesArray;
  43. }
  44. /** Callocs a pb_bytes_array and copies the given NSString's bytes into the bytes array.
  45. *
  46. * @note Memory needs to be free manually, through pb_free or pb_release.
  47. * @param string The string to encode as pb_bytes.
  48. */
  49. pb_bytes_array_t *FIRMessagingEncodeString(NSString *string) {
  50. NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding];
  51. return FIRMessagingEncodeData(stringBytes);
  52. }
  53. @interface FIRMessagingMetricsLog : NSObject <GDTCOREventDataObject>
  54. @property(nonatomic) fm_MessagingClientEventExtension eventExtension;
  55. @end
  56. @implementation FIRMessagingMetricsLog
  57. - (instancetype)initWithEventExtension:(fm_MessagingClientEventExtension)eventExtension {
  58. self = [super init];
  59. if (self) {
  60. _eventExtension = eventExtension;
  61. }
  62. return self;
  63. }
  64. - (NSData *)transportBytes {
  65. pb_ostream_t sizestream = PB_OSTREAM_SIZING;
  66. // Encode 1 time to determine the size.
  67. if (!pb_encode(&sizestream, fm_MessagingClientEventExtension_fields, &_eventExtension)) {
  68. FIRMessagingLoggerError(kFIRMessagingServiceExtensionTransportBytesError,
  69. @"Error in nanopb encoding for size: %s", PB_GET_ERROR(&sizestream));
  70. }
  71. // Encode a 2nd time to actually get the bytes from it.
  72. size_t bufferSize = sizestream.bytes_written;
  73. CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize);
  74. CFDataSetLength(dataRef, bufferSize);
  75. pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize);
  76. if (!pb_encode(&ostream, fm_MessagingClientEventExtension_fields, &_eventExtension)) {
  77. FIRMessagingLoggerError(kFIRMessagingServiceExtensionTransportBytesError,
  78. @"Error in nanopb encoding for bytes: %s", PB_GET_ERROR(&ostream));
  79. }
  80. CFDataSetLength(dataRef, ostream.bytes_written);
  81. return CFBridgingRelease(dataRef);
  82. }
  83. @end
  84. @implementation FIRMessagingExtensionHelper
  85. - (void)populateNotificationContent:(UNMutableNotificationContent *)content
  86. withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
  87. __block void (^handler)(UNNotificationContent *_Nonnull) = [contentHandler copy];
  88. __block UNMutableNotificationContent *bestAttemptContent = [content mutableCopy];
  89. // The `userInfo` property isn't available on newer versions of tvOS.
  90. #if !TARGET_OS_TV
  91. NSObject *currentImageURL = content.userInfo[kPayloadOptionsName][kPayloadOptionsImageURLName];
  92. if (!currentImageURL || currentImageURL == [NSNull null]) {
  93. [self deliverNotificationWithContent:bestAttemptContent handler:handler];
  94. return;
  95. }
  96. NSURL *attachmentURL = [NSURL URLWithString:(NSString *)currentImageURL];
  97. if (attachmentURL) {
  98. [self loadAttachmentForURL:attachmentURL
  99. completionHandler:^(UNNotificationAttachment *attachment) {
  100. if (attachment != nil) {
  101. bestAttemptContent.attachments = @[ attachment ];
  102. }
  103. [self deliverNotificationWithContent:bestAttemptContent handler:handler];
  104. }];
  105. } else {
  106. FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageInvalidURL,
  107. @"The Image URL provided is invalid %@.", currentImageURL);
  108. [self deliverNotificationWithContent:bestAttemptContent handler:handler];
  109. }
  110. #else // !TARGET_OS_TV
  111. [self deliverNotification];
  112. #endif // !TARGET_OS_TV
  113. }
  114. #if !TARGET_OS_TV
  115. - (NSString *)fileExtensionForResponse:(NSURLResponse *)response {
  116. NSString *suggestedPathExtension = [response.suggestedFilename pathExtension];
  117. if (suggestedPathExtension.length > 0) {
  118. return [NSString stringWithFormat:@".%@", suggestedPathExtension];
  119. }
  120. if ([response.MIMEType containsString:kImagePathPrefix]) {
  121. return [response.MIMEType stringByReplacingOccurrencesOfString:kImagePathPrefix
  122. withString:@"."];
  123. }
  124. return kNoExtension;
  125. }
  126. - (void)loadAttachmentForURL:(NSURL *)attachmentURL
  127. completionHandler:
  128. (void (^NS_SWIFT_SENDABLE)(UNNotificationAttachment *))completionHandler
  129. NS_SWIFT_SENDABLE {
  130. NSURLSession *session = [NSURLSession
  131. sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
  132. [[session
  133. downloadTaskWithURL:attachmentURL
  134. completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
  135. UNNotificationAttachment *attachment = nil;
  136. if (error != nil) {
  137. FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotDownloaded,
  138. @"Failed to download image given URL %@, error: %@\n",
  139. attachmentURL, error);
  140. completionHandler(attachment);
  141. return;
  142. }
  143. NSFileManager *fileManager = [NSFileManager defaultManager];
  144. NSString *fileExtension = [self fileExtensionForResponse:response];
  145. NSURL *localURL = [NSURL
  146. fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExtension]];
  147. [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
  148. if (error) {
  149. FIRMessagingLoggerError(
  150. kFIRMessagingServiceExtensionLocalFileNotCreated,
  151. @"Failed to move the image file to local location: %@, error: %@\n", localURL,
  152. error);
  153. completionHandler(attachment);
  154. return;
  155. }
  156. attachment = [UNNotificationAttachment attachmentWithIdentifier:@""
  157. URL:localURL
  158. options:nil
  159. error:&error];
  160. if (error) {
  161. FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotAttached,
  162. @"Failed to create attachment with URL %@, error: %@\n",
  163. localURL, error);
  164. completionHandler(attachment);
  165. return;
  166. }
  167. completionHandler(attachment);
  168. }] resume];
  169. }
  170. #endif // !TARGET_OS_TV
  171. - (void)deliverNotificationWithContent:(UNNotificationContent *)content
  172. handler:(void (^_Nullable)(UNNotificationContent *_Nonnull))handler {
  173. if (handler) {
  174. handler(content);
  175. }
  176. }
  177. - (void)exportDeliveryMetricsToBigQueryWithMessageInfo:(NSDictionary *)info {
  178. GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"1249"
  179. transformers:nil
  180. target:kGDTCORTargetCCT];
  181. fm_MessagingClientEventExtension eventExtension = fm_MessagingClientEventExtension_init_default;
  182. fm_MessagingClientEvent clientEvent = fm_MessagingClientEvent_init_default;
  183. if (!info[kFIRMessagingSenderID]) {
  184. FIRMessagingLoggerError(kFIRMessagingServiceExtensionInvalidProjectID,
  185. @"Delivery logging failed: Invalid project ID");
  186. return;
  187. }
  188. clientEvent.project_number = (int64_t)[info[kFIRMessagingSenderID] longLongValue];
  189. if (!info[kFIRMessagingMessageIDKey] ||
  190. ![info[kFIRMessagingMessageIDKey] isKindOfClass:NSString.class]) {
  191. FIRMessagingLoggerWarn(kFIRMessagingServiceExtensionInvalidMessageID,
  192. @"Delivery logging failed: Invalid Message ID");
  193. return;
  194. }
  195. clientEvent.message_id = FIRMessagingEncodeString(info[kFIRMessagingMessageIDKey]);
  196. if (!info[kFIRMessagingFID] || ![info[kFIRMessagingFID] isKindOfClass:NSString.class]) {
  197. FIRMessagingLoggerWarn(kFIRMessagingServiceExtensionInvalidInstanceID,
  198. @"Delivery logging failed: Invalid Instance ID");
  199. return;
  200. }
  201. clientEvent.instance_id = FIRMessagingEncodeString(info[kFIRMessagingFID]);
  202. if ([info[@"aps"][kFIRMessagingMessageAPNSContentAvailableKey] intValue] == 1 &&
  203. ![GULAppEnvironmentUtil isAppExtension]) {
  204. clientEvent.message_type = fm_MessagingClientEvent_MessageType_DATA_MESSAGE;
  205. } else {
  206. clientEvent.message_type = fm_MessagingClientEvent_MessageType_DISPLAY_NOTIFICATION;
  207. }
  208. clientEvent.sdk_platform = fm_MessagingClientEvent_SDKPlatform_IOS;
  209. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  210. if ([GULAppEnvironmentUtil isAppExtension]) {
  211. bundleID = [[self class] bundleIdentifierByRemovingLastPartFrom:bundleID];
  212. }
  213. if (bundleID) {
  214. clientEvent.package_name = FIRMessagingEncodeString(bundleID);
  215. }
  216. clientEvent.event = fm_MessagingClientEvent_Event_MESSAGE_DELIVERED;
  217. if (info[kFIRMessagingAnalyticsMessageLabel]) {
  218. clientEvent.analytics_label =
  219. FIRMessagingEncodeString(info[kFIRMessagingAnalyticsMessageLabel]);
  220. }
  221. if (info[kFIRMessagingAnalyticsComposerIdentifier]) {
  222. clientEvent.campaign_id =
  223. (int64_t)[info[kFIRMessagingAnalyticsComposerIdentifier] longLongValue];
  224. }
  225. if (info[kFIRMessagingAnalyticsComposerLabel]) {
  226. clientEvent.composer_label =
  227. FIRMessagingEncodeString(info[kFIRMessagingAnalyticsComposerLabel]);
  228. }
  229. eventExtension.messaging_client_event = &clientEvent;
  230. FIRMessagingMetricsLog *log =
  231. [[FIRMessagingMetricsLog alloc] initWithEventExtension:eventExtension];
  232. GDTCOREvent *event;
  233. if (info[kFIRMessagingProductID]) {
  234. int32_t productID = [info[kFIRMessagingProductID] intValue];
  235. GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:productID];
  236. event = [transport eventForTransportWithProductData:productData];
  237. } else {
  238. event = [transport eventForTransport];
  239. }
  240. event.dataObject = log;
  241. event.qosTier = GDTCOREventQoSFast;
  242. // Use this API for SDK service data events.
  243. [transport sendDataEvent:event];
  244. }
  245. + (NSString *)bundleIdentifierByRemovingLastPartFrom:(NSString *)bundleIdentifier {
  246. NSString *bundleIDComponentsSeparator = @".";
  247. NSMutableArray<NSString *> *bundleIDComponents =
  248. [[bundleIdentifier componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy];
  249. [bundleIDComponents removeLastObject];
  250. return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator];
  251. }
  252. @end