FIRMessagingExtensionHelper.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. @interface FIRMessagingExtensionHelper ()
  85. @property(nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
  86. @property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
  87. @end
  88. @implementation FIRMessagingExtensionHelper
  89. - (void)populateNotificationContent:(UNMutableNotificationContent *)content
  90. withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
  91. self.contentHandler = [contentHandler copy];
  92. self.bestAttemptContent = content;
  93. // The `userInfo` property isn't available on newer versions of tvOS.
  94. #if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_WATCH
  95. NSObject *currentImageURL = content.userInfo[kPayloadOptionsName][kPayloadOptionsImageURLName];
  96. if (!currentImageURL || currentImageURL == [NSNull null]) {
  97. [self deliverNotification];
  98. return;
  99. }
  100. NSURL *attachmentURL = [NSURL URLWithString:(NSString *)currentImageURL];
  101. if (attachmentURL) {
  102. [self loadAttachmentForURL:attachmentURL
  103. completionHandler:^(UNNotificationAttachment *attachment) {
  104. if (attachment != nil) {
  105. self.bestAttemptContent.attachments = @[ attachment ];
  106. }
  107. [self deliverNotification];
  108. }];
  109. } else {
  110. FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageInvalidURL,
  111. @"The Image URL provided is invalid %@.", currentImageURL);
  112. [self deliverNotification];
  113. }
  114. #else
  115. [self deliverNotification];
  116. #endif
  117. }
  118. #if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_WATCH
  119. - (NSString *)fileExtensionForResponse:(NSURLResponse *)response {
  120. NSString *suggestedPathExtension = [response.suggestedFilename pathExtension];
  121. if (suggestedPathExtension.length > 0) {
  122. return [NSString stringWithFormat:@".%@", suggestedPathExtension];
  123. }
  124. if ([response.MIMEType containsString:kImagePathPrefix]) {
  125. return [response.MIMEType stringByReplacingOccurrencesOfString:kImagePathPrefix
  126. withString:@"."];
  127. }
  128. return kNoExtension;
  129. }
  130. - (void)loadAttachmentForURL:(NSURL *)attachmentURL
  131. completionHandler:(void (^)(UNNotificationAttachment *))completionHandler {
  132. __block UNNotificationAttachment *attachment = nil;
  133. NSURLSession *session = [NSURLSession
  134. sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
  135. [[session
  136. downloadTaskWithURL:attachmentURL
  137. completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
  138. if (error != nil) {
  139. FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotDownloaded,
  140. @"Failed to download image given URL %@, error: %@\n",
  141. attachmentURL, error);
  142. completionHandler(attachment);
  143. return;
  144. }
  145. NSFileManager *fileManager = [NSFileManager defaultManager];
  146. NSString *fileExtension = [self fileExtensionForResponse:response];
  147. NSURL *localURL = [NSURL
  148. fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExtension]];
  149. [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
  150. if (error) {
  151. FIRMessagingLoggerError(
  152. kFIRMessagingServiceExtensionLocalFileNotCreated,
  153. @"Failed to move the image file to local location: %@, error: %@\n", localURL,
  154. error);
  155. completionHandler(attachment);
  156. return;
  157. }
  158. attachment = [UNNotificationAttachment attachmentWithIdentifier:@""
  159. URL:localURL
  160. options:nil
  161. error:&error];
  162. if (error) {
  163. FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotAttached,
  164. @"Failed to create attachment with URL %@, error: %@\n",
  165. localURL, error);
  166. completionHandler(attachment);
  167. return;
  168. }
  169. completionHandler(attachment);
  170. }] resume];
  171. }
  172. #endif
  173. - (void)deliverNotification {
  174. if (self.contentHandler) {
  175. self.contentHandler(self.bestAttemptContent);
  176. }
  177. }
  178. - (void)exportDeliveryMetricsToBigQueryWithMessageInfo:(NSDictionary *)info {
  179. GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"1249"
  180. transformers:nil
  181. target:kGDTCORTargetFLL];
  182. fm_MessagingClientEventExtension eventExtension = fm_MessagingClientEventExtension_init_default;
  183. fm_MessagingClientEvent clientEvent = fm_MessagingClientEvent_init_default;
  184. if (!info[kFIRMessagingSenderID]) {
  185. FIRMessagingLoggerError(kFIRMessagingServiceExtensionInvalidProjectID,
  186. @"Delivery logging failed: Invalid project ID");
  187. return;
  188. }
  189. clientEvent.project_number = (int64_t)[info[kFIRMessagingSenderID] longLongValue];
  190. if (!info[kFIRMessagingMessageIDKey] ||
  191. ![info[kFIRMessagingMessageIDKey] isKindOfClass:NSString.class]) {
  192. FIRMessagingLoggerWarn(kFIRMessagingServiceExtensionInvalidMessageID,
  193. @"Delivery logging failed: Invalid Message ID");
  194. return;
  195. }
  196. clientEvent.message_id = FIRMessagingEncodeString(info[kFIRMessagingMessageIDKey]);
  197. if (!info[kFIRMessagingFID] || ![info[kFIRMessagingFID] isKindOfClass:NSString.class]) {
  198. FIRMessagingLoggerWarn(kFIRMessagingServiceExtensionInvalidInstanceID,
  199. @"Delivery logging failed: Invalid Instance ID");
  200. return;
  201. }
  202. clientEvent.instance_id = FIRMessagingEncodeString(info[kFIRMessagingFID]);
  203. if ([info[@"aps"][kFIRMessagingMessageAPNSContentAvailableKey] intValue] == 1 &&
  204. ![GULAppEnvironmentUtil isAppExtension]) {
  205. clientEvent.message_type = fm_MessagingClientEvent_MessageType_DATA_MESSAGE;
  206. } else {
  207. clientEvent.message_type = fm_MessagingClientEvent_MessageType_DISPLAY_NOTIFICATION;
  208. }
  209. clientEvent.sdk_platform = fm_MessagingClientEvent_SDKPlatform_IOS;
  210. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  211. if ([GULAppEnvironmentUtil isAppExtension]) {
  212. bundleID = [[self class] bundleIdentifierByRemovingLastPartFrom:bundleID];
  213. }
  214. if (bundleID) {
  215. clientEvent.package_name = FIRMessagingEncodeString(bundleID);
  216. }
  217. clientEvent.event = fm_MessagingClientEvent_Event_MESSAGE_DELIVERED;
  218. if (info[kFIRMessagingAnalyticsMessageLabel]) {
  219. clientEvent.analytics_label =
  220. FIRMessagingEncodeString(info[kFIRMessagingAnalyticsMessageLabel]);
  221. }
  222. if (info[kFIRMessagingAnalyticsComposerIdentifier]) {
  223. clientEvent.campaign_id =
  224. (int64_t)[info[kFIRMessagingAnalyticsComposerIdentifier] longLongValue];
  225. }
  226. if (info[kFIRMessagingAnalyticsComposerLabel]) {
  227. clientEvent.composer_label =
  228. FIRMessagingEncodeString(info[kFIRMessagingAnalyticsComposerLabel]);
  229. }
  230. eventExtension.messaging_client_event = &clientEvent;
  231. FIRMessagingMetricsLog *log =
  232. [[FIRMessagingMetricsLog alloc] initWithEventExtension:eventExtension];
  233. GDTCOREvent *event = [transport eventForTransport];
  234. event.dataObject = log;
  235. event.qosTier = GDTCOREventQoSFast;
  236. // Use this API for SDK service data events.
  237. [transport sendDataEvent:event];
  238. }
  239. + (NSString *)bundleIdentifierByRemovingLastPartFrom:(NSString *)bundleIdentifier {
  240. NSString *bundleIDComponentsSeparator = @".";
  241. NSMutableArray<NSString *> *bundleIDComponents =
  242. [[bundleIdentifier componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy];
  243. [bundleIDComponents removeLastObject];
  244. return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator];
  245. }
  246. @end