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