FIRMessagingAnalytics.m 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*
  2. * Copyright 2018 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 "FirebaseMessaging/Sources/FIRMessagingAnalytics.h"
  17. #import <GoogleUtilities/GULAppDelegateSwizzler.h>
  18. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  19. #import "Interop/Analytics/Public/FIRInteropEventNames.h"
  20. #import "Interop/Analytics/Public/FIRInteropParameterNames.h"
  21. #import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
  22. #import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
  23. static NSString *const kLogTag = @"FIRMessagingAnalytics";
  24. // aps Key
  25. static NSString *const kApsKey = @"aps";
  26. static NSString *const kApsAlertKey = @"alert";
  27. static NSString *const kApsSoundKey = @"sound";
  28. static NSString *const kApsBadgeKey = @"badge";
  29. static NSString *const kApsContentAvailableKey = @"badge";
  30. // Data Key
  31. static NSString *const kDataKey = @"data";
  32. static NSString *const kFIRParameterLabel = @"label";
  33. static NSString *const kReengagementSource = @"Firebase";
  34. static NSString *const kReengagementMedium = @"notification";
  35. // Analytics
  36. static NSString *const kAnalyticsEnabled = @"google.c.a.e";
  37. static NSString *const kAnalyticsMessageTimestamp = @"google.c.a.ts";
  38. static NSString *const kAnalyticsMessageUseDeviceTime = @"google.c.a.udt";
  39. static NSString *const kAnalyticsTrackConversions = @"google.c.a.tc";
  40. @implementation FIRMessagingAnalytics
  41. + (BOOL)canLogNotification:(NSDictionary *)notification {
  42. if (!notification.count) {
  43. // Payload is empty
  44. return NO;
  45. }
  46. NSString *isAnalyticsLoggingEnabled = notification[kAnalyticsEnabled];
  47. if (![isAnalyticsLoggingEnabled isKindOfClass:[NSString class]] ||
  48. ![isAnalyticsLoggingEnabled isEqualToString:@"1"]) {
  49. // Analytics logging is not enabled
  50. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAnalytics001,
  51. @"Analytics logging is disabled. Do not log event.");
  52. return NO;
  53. }
  54. return YES;
  55. }
  56. + (void)logOpenNotification:(NSDictionary *)notification
  57. toAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
  58. [self logUserPropertyForConversionTracking:notification toAnalytics:analytics];
  59. [self logEvent:kFIRIEventNotificationOpen withNotification:notification toAnalytics:analytics];
  60. }
  61. + (void)logForegroundNotification:(NSDictionary *)notification
  62. toAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
  63. [self logEvent:kFIRIEventNotificationForeground
  64. withNotification:notification
  65. toAnalytics:analytics];
  66. }
  67. + (void)logEvent:(NSString *)event
  68. withNotification:(NSDictionary *)notification
  69. toAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
  70. if (!event.length) {
  71. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAnalyticsInvalidEvent,
  72. @"Can't log analytics with empty event.");
  73. return;
  74. }
  75. NSMutableDictionary *params = [self paramsForEvent:event withNotification:notification];
  76. [analytics logEventWithOrigin:@"fcm" name:event parameters:params];
  77. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAnalytics005, @"%@: Sending event: %@ params: %@",
  78. kLogTag, event, params);
  79. }
  80. + (NSMutableDictionary *)paramsForEvent:(NSString *)event
  81. withNotification:(NSDictionary *)notification {
  82. NSDictionary *analyticsDataMap = notification;
  83. if (!analyticsDataMap.count) {
  84. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAnalytics000,
  85. @"No data found in notification. Will not log any analytics events.");
  86. return nil;
  87. }
  88. if (![self canLogNotification:analyticsDataMap]) {
  89. return nil;
  90. }
  91. NSMutableDictionary *params = [NSMutableDictionary dictionary];
  92. NSString *composerIdentifier = analyticsDataMap[kFIRMessagingAnalyticsComposerIdentifier];
  93. if ([composerIdentifier isKindOfClass:[NSString class]] && composerIdentifier.length) {
  94. params[kFIRIParameterMessageIdentifier] = [composerIdentifier copy];
  95. }
  96. NSString *composerLabel = analyticsDataMap[kFIRMessagingAnalyticsComposerLabel];
  97. if ([composerLabel isKindOfClass:[NSString class]] && composerLabel.length) {
  98. params[kFIRIParameterMessageName] = [composerLabel copy];
  99. }
  100. NSString *messageLabel = analyticsDataMap[kFIRMessagingAnalyticsMessageLabel];
  101. if ([messageLabel isKindOfClass:[NSString class]] && messageLabel.length) {
  102. params[kFIRParameterLabel] = [messageLabel copy];
  103. }
  104. NSString *from = analyticsDataMap[kFIRMessagingFromKey];
  105. if ([from isKindOfClass:[NSString class]] && [from containsString:@"/topics/"]) {
  106. params[kFIRIParameterTopic] = [from copy];
  107. }
  108. id timestamp = analyticsDataMap[kAnalyticsMessageTimestamp];
  109. if ([timestamp respondsToSelector:@selector(longLongValue)]) {
  110. int64_t timestampValue = [timestamp longLongValue];
  111. if (timestampValue != 0) {
  112. params[kFIRIParameterMessageTime] = @(timestampValue);
  113. }
  114. }
  115. if (analyticsDataMap[kAnalyticsMessageUseDeviceTime]) {
  116. params[kFIRIParameterMessageDeviceTime] = analyticsDataMap[kAnalyticsMessageUseDeviceTime];
  117. }
  118. return params;
  119. }
  120. + (void)logUserPropertyForConversionTracking:(NSDictionary *)notification
  121. toAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
  122. NSInteger shouldTrackConversions = [notification[kAnalyticsTrackConversions] integerValue];
  123. if (shouldTrackConversions != 1) {
  124. return;
  125. }
  126. NSString *composerIdentifier = notification[kFIRMessagingAnalyticsComposerIdentifier];
  127. if ([composerIdentifier isKindOfClass:[NSString class]] && composerIdentifier.length) {
  128. // Set user property for event.
  129. [analytics setUserPropertyWithOrigin:@"fcm"
  130. name:kFIRIUserPropertyLastNotification
  131. value:composerIdentifier];
  132. // Set the re-engagement attribution properties.
  133. NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:3];
  134. params[kFIRIParameterSource] = kReengagementSource;
  135. params[kFIRIParameterMedium] = kReengagementMedium;
  136. params[kFIRIParameterCampaign] = composerIdentifier;
  137. [analytics logEventWithOrigin:@"fcm" name:kFIRIEventFirebaseCampaign parameters:params];
  138. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAnalytics003,
  139. @"%@: Sending event: %@ params: %@", kLogTag,
  140. kFIRIEventFirebaseCampaign, params);
  141. } else {
  142. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAnalytics004,
  143. @"%@: Failed to set user property: %@ value: %@", kLogTag,
  144. kFIRIUserPropertyLastNotification, composerIdentifier);
  145. }
  146. }
  147. + (void)logMessage:(NSDictionary *)notification
  148. toAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
  149. // iOS only because Analytics doesn't support other platforms.
  150. #if TARGET_OS_IOS
  151. if (![self canLogNotification:notification]) {
  152. return;
  153. }
  154. UIApplication *application = [GULAppDelegateSwizzler sharedApplication];
  155. if (!application) {
  156. return;
  157. }
  158. UIApplicationState applicationState = application.applicationState;
  159. switch (applicationState) {
  160. case UIApplicationStateInactive:
  161. // App was in background and in transition to open when user tapped
  162. // on a display notification.
  163. // Needs to check notification is displayed.
  164. if ([[self class] isDisplayNotification:notification]) {
  165. [self logOpenNotification:notification toAnalytics:analytics];
  166. }
  167. break;
  168. case UIApplicationStateActive:
  169. // App was in foreground when it received the notification.
  170. [self logForegroundNotification:notification toAnalytics:analytics];
  171. break;
  172. default:
  173. // App was either in background state or in transition from closed
  174. // to open.
  175. // Needs to check notification is displayed.
  176. if ([[self class] isDisplayNotification:notification]) {
  177. [self logOpenNotification:notification toAnalytics:analytics];
  178. }
  179. break;
  180. }
  181. #endif
  182. }
  183. + (BOOL)isDisplayNotification:(NSDictionary *)notification {
  184. NSDictionary *aps = notification[kApsKey];
  185. if (!aps || ![aps isKindOfClass:[NSDictionary class]]) {
  186. return NO;
  187. }
  188. NSDictionary *alert = aps[kApsAlertKey];
  189. if (!alert) {
  190. return NO;
  191. }
  192. if ([alert isKindOfClass:[NSDictionary class]]) {
  193. return alert.allKeys.count > 0;
  194. }
  195. // alert can be string sometimes (if only body is specified)
  196. if ([alert isKindOfClass:[NSString class]]) {
  197. return YES;
  198. }
  199. return NO;
  200. }
  201. @end