| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- /*
- * Copyright 2019 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 <nanopb/pb.h>
- #import <nanopb/pb_decode.h>
- #import <nanopb/pb_encode.h>
- #import <GoogleDataTransport/GoogleDataTransport.h>
- #import <GoogleUtilities/GULAppEnvironmentUtil.h>
- #import "FirebaseMessaging/Sources/FIRMessagingCode.h"
- #import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
- #import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
- #import "FirebaseMessaging/Sources/Protogen/nanopb/me.nanopb.h"
- #import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessagingExtensionHelper.h"
- static NSString *const kPayloadOptionsName = @"fcm_options";
- static NSString *const kPayloadOptionsImageURLName = @"image";
- static NSString *const kNoExtension = @"";
- static NSString *const kImagePathPrefix = @"image/";
- #pragma mark - nanopb helper functions
- /** Callocs a pb_bytes_array and copies the given NSData bytes into the bytes array.
- *
- * @note Memory needs to be free manually, through pb_free or pb_release.
- * @param data The data to copy into the new bytes array.
- */
- pb_bytes_array_t *FIRMessagingEncodeData(NSData *data) {
- pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length));
- if (pbBytesArray != NULL) {
- [data getBytes:pbBytesArray->bytes length:data.length];
- pbBytesArray->size = (pb_size_t)data.length;
- }
- return pbBytesArray;
- }
- /** Callocs a pb_bytes_array and copies the given NSString's bytes into the bytes array.
- *
- * @note Memory needs to be free manually, through pb_free or pb_release.
- * @param string The string to encode as pb_bytes.
- */
- pb_bytes_array_t *FIRMessagingEncodeString(NSString *string) {
- NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding];
- return FIRMessagingEncodeData(stringBytes);
- }
- @interface FIRMessagingMetricsLog : NSObject <GDTCOREventDataObject>
- @property(nonatomic) fm_MessagingClientEventExtension eventExtension;
- @end
- @implementation FIRMessagingMetricsLog
- - (instancetype)initWithEventExtension:(fm_MessagingClientEventExtension)eventExtension {
- self = [super init];
- if (self) {
- _eventExtension = eventExtension;
- }
- return self;
- }
- - (NSData *)transportBytes {
- pb_ostream_t sizestream = PB_OSTREAM_SIZING;
- // Encode 1 time to determine the size.
- if (!pb_encode(&sizestream, fm_MessagingClientEventExtension_fields, &_eventExtension)) {
- FIRMessagingLoggerError(kFIRMessagingServiceExtensionTransportBytesError,
- @"Error in nanopb encoding for size: %s", PB_GET_ERROR(&sizestream));
- }
- // Encode a 2nd time to actually get the bytes from it.
- size_t bufferSize = sizestream.bytes_written;
- CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize);
- CFDataSetLength(dataRef, bufferSize);
- pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize);
- if (!pb_encode(&ostream, fm_MessagingClientEventExtension_fields, &_eventExtension)) {
- FIRMessagingLoggerError(kFIRMessagingServiceExtensionTransportBytesError,
- @"Error in nanopb encoding for bytes: %s", PB_GET_ERROR(&ostream));
- }
- CFDataSetLength(dataRef, ostream.bytes_written);
- return CFBridgingRelease(dataRef);
- }
- @end
- @interface FIRMessagingExtensionHelper ()
- @property(nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
- @property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
- @end
- @implementation FIRMessagingExtensionHelper
- - (void)populateNotificationContent:(UNMutableNotificationContent *)content
- withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
- self.contentHandler = [contentHandler copy];
- self.bestAttemptContent = content;
- // The `userInfo` property isn't available on newer versions of tvOS.
- #if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_WATCH
- NSObject *currentImageURL = content.userInfo[kPayloadOptionsName][kPayloadOptionsImageURLName];
- if (!currentImageURL || currentImageURL == [NSNull null]) {
- [self deliverNotification];
- return;
- }
- NSURL *attachmentURL = [NSURL URLWithString:(NSString *)currentImageURL];
- if (attachmentURL) {
- [self loadAttachmentForURL:attachmentURL
- completionHandler:^(UNNotificationAttachment *attachment) {
- if (attachment != nil) {
- self.bestAttemptContent.attachments = @[ attachment ];
- }
- [self deliverNotification];
- }];
- } else {
- FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageInvalidURL,
- @"The Image URL provided is invalid %@.", currentImageURL);
- [self deliverNotification];
- }
- #else
- [self deliverNotification];
- #endif
- }
- #if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_WATCH
- - (NSString *)fileExtensionForResponse:(NSURLResponse *)response {
- NSString *suggestedPathExtension = [response.suggestedFilename pathExtension];
- if (suggestedPathExtension.length > 0) {
- return [NSString stringWithFormat:@".%@", suggestedPathExtension];
- }
- if ([response.MIMEType containsString:kImagePathPrefix]) {
- return [response.MIMEType stringByReplacingOccurrencesOfString:kImagePathPrefix
- withString:@"."];
- }
- return kNoExtension;
- }
- - (void)loadAttachmentForURL:(NSURL *)attachmentURL
- completionHandler:(void (^)(UNNotificationAttachment *))completionHandler {
- __block UNNotificationAttachment *attachment = nil;
- NSURLSession *session = [NSURLSession
- sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
- [[session
- downloadTaskWithURL:attachmentURL
- completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
- if (error != nil) {
- FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotDownloaded,
- @"Failed to download image given URL %@, error: %@\n",
- attachmentURL, error);
- completionHandler(attachment);
- return;
- }
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *fileExtension = [self fileExtensionForResponse:response];
- NSURL *localURL = [NSURL
- fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExtension]];
- [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
- if (error) {
- FIRMessagingLoggerError(
- kFIRMessagingServiceExtensionLocalFileNotCreated,
- @"Failed to move the image file to local location: %@, error: %@\n", localURL,
- error);
- completionHandler(attachment);
- return;
- }
- attachment = [UNNotificationAttachment attachmentWithIdentifier:@""
- URL:localURL
- options:nil
- error:&error];
- if (error) {
- FIRMessagingLoggerError(kFIRMessagingServiceExtensionImageNotAttached,
- @"Failed to create attachment with URL %@, error: %@\n",
- localURL, error);
- completionHandler(attachment);
- return;
- }
- completionHandler(attachment);
- }] resume];
- }
- #endif
- - (void)deliverNotification {
- if (self.contentHandler) {
- self.contentHandler(self.bestAttemptContent);
- }
- }
- - (void)exportDeliveryMetricsToBigQueryWithMessageInfo:(NSDictionary *)info {
- GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"1249"
- transformers:nil
- target:kGDTCORTargetCCT];
- fm_MessagingClientEventExtension eventExtension = fm_MessagingClientEventExtension_init_default;
- fm_MessagingClientEvent clientEvent = fm_MessagingClientEvent_init_default;
- if (!info[kFIRMessagingSenderID]) {
- FIRMessagingLoggerError(kFIRMessagingServiceExtensionInvalidProjectID,
- @"Delivery logging failed: Invalid project ID");
- return;
- }
- clientEvent.project_number = (int64_t)[info[kFIRMessagingSenderID] longLongValue];
- if (!info[kFIRMessagingMessageIDKey] ||
- ![info[kFIRMessagingMessageIDKey] isKindOfClass:NSString.class]) {
- FIRMessagingLoggerWarn(kFIRMessagingServiceExtensionInvalidMessageID,
- @"Delivery logging failed: Invalid Message ID");
- return;
- }
- clientEvent.message_id = FIRMessagingEncodeString(info[kFIRMessagingMessageIDKey]);
- if (!info[kFIRMessagingFID] || ![info[kFIRMessagingFID] isKindOfClass:NSString.class]) {
- FIRMessagingLoggerWarn(kFIRMessagingServiceExtensionInvalidInstanceID,
- @"Delivery logging failed: Invalid Instance ID");
- return;
- }
- clientEvent.instance_id = FIRMessagingEncodeString(info[kFIRMessagingFID]);
- if ([info[@"aps"][kFIRMessagingMessageAPNSContentAvailableKey] intValue] == 1 &&
- ![GULAppEnvironmentUtil isAppExtension]) {
- clientEvent.message_type = fm_MessagingClientEvent_MessageType_DATA_MESSAGE;
- } else {
- clientEvent.message_type = fm_MessagingClientEvent_MessageType_DISPLAY_NOTIFICATION;
- }
- clientEvent.sdk_platform = fm_MessagingClientEvent_SDKPlatform_IOS;
- NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
- if ([GULAppEnvironmentUtil isAppExtension]) {
- bundleID = [[self class] bundleIdentifierByRemovingLastPartFrom:bundleID];
- }
- if (bundleID) {
- clientEvent.package_name = FIRMessagingEncodeString(bundleID);
- }
- clientEvent.event = fm_MessagingClientEvent_Event_MESSAGE_DELIVERED;
- if (info[kFIRMessagingAnalyticsMessageLabel]) {
- clientEvent.analytics_label =
- FIRMessagingEncodeString(info[kFIRMessagingAnalyticsMessageLabel]);
- }
- if (info[kFIRMessagingAnalyticsComposerIdentifier]) {
- clientEvent.campaign_id =
- (int64_t)[info[kFIRMessagingAnalyticsComposerIdentifier] longLongValue];
- }
- if (info[kFIRMessagingAnalyticsComposerLabel]) {
- clientEvent.composer_label =
- FIRMessagingEncodeString(info[kFIRMessagingAnalyticsComposerLabel]);
- }
- eventExtension.messaging_client_event = &clientEvent;
- FIRMessagingMetricsLog *log =
- [[FIRMessagingMetricsLog alloc] initWithEventExtension:eventExtension];
- GDTCOREvent *event = [transport eventForTransport];
- event.dataObject = log;
- event.qosTier = GDTCOREventQoSFast;
- // Use this API for SDK service data events.
- [transport sendDataEvent:event];
- }
- + (NSString *)bundleIdentifierByRemovingLastPartFrom:(NSString *)bundleIdentifier {
- NSString *bundleIDComponentsSeparator = @".";
- NSMutableArray<NSString *> *bundleIDComponents =
- [[bundleIdentifier componentsSeparatedByString:bundleIDComponentsSeparator] mutableCopy];
- [bundleIDComponents removeLastObject];
- return [bundleIDComponents componentsJoinedByString:bundleIDComponentsSeparator];
- }
- @end
|