mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
295 lines
12 KiB
Objective-C
295 lines
12 KiB
Objective-C
/*
|
|
* 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_TV
|
|
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 // !TARGET_OS_TV
|
|
[self deliverNotification];
|
|
#endif // !TARGET_OS_TV
|
|
}
|
|
|
|
#if !TARGET_OS_TV
|
|
- (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 // !TARGET_OS_TV
|
|
|
|
- (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;
|
|
if (info[kFIRMessagingProductID]) {
|
|
int32_t productID = [info[kFIRMessagingProductID] intValue];
|
|
GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:productID];
|
|
event = [transport eventForTransportWithProductData:productData];
|
|
} else {
|
|
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
|