/FBSDKCoreKit/FBSDKCoreKit/FBSDKAppEvents.m

http://github.com/facebook/facebook-ios-sdk · Objective C · 890 lines · 682 code · 122 blank · 86 comment · 87 complexity · be15192aa5f9ba6853c18d7a33344964 MD5 · raw file

  1. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  2. //
  3. // You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
  4. // copy, modify, and distribute this software in source code or binary form for use
  5. // in connection with the web services and APIs provided by Facebook.
  6. //
  7. // As with any software that integrates with the Facebook platform, your use of
  8. // this software is subject to the Facebook Developer Principles and Policies
  9. // [http://developers.facebook.com/policy/]. This copyright notice shall be
  10. // included in all copies or substantial portions of the software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  14. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  15. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  16. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  17. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  18. #import "FBSDKAppEvents.h"
  19. #import "FBSDKAppEvents+Internal.h"
  20. #import <UIKit/UIApplication.h>
  21. #import "FBSDKAccessToken.h"
  22. #import "FBSDKAppEventsState.h"
  23. #import "FBSDKAppEventsStateManager.h"
  24. #import "FBSDKAppEventsUtility.h"
  25. #import "FBSDKConstants.h"
  26. #import "FBSDKError.h"
  27. #import "FBSDKGraphRequest+Internal.h"
  28. #import "FBSDKInternalUtility.h"
  29. #import "FBSDKLogger.h"
  30. #import "FBSDKPaymentObserver.h"
  31. #import "FBSDKServerConfiguration.h"
  32. #import "FBSDKServerConfigurationManager.h"
  33. #import "FBSDKSettings.h"
  34. #import "FBSDKTimeSpentData.h"
  35. #import "FBSDKUtility.h"
  36. //
  37. // Public event names
  38. //
  39. // General purpose
  40. NSString *const FBSDKAppEventNameCompletedRegistration = @"fb_mobile_complete_registration";
  41. NSString *const FBSDKAppEventNameViewedContent = @"fb_mobile_content_view";
  42. NSString *const FBSDKAppEventNameSearched = @"fb_mobile_search";
  43. NSString *const FBSDKAppEventNameRated = @"fb_mobile_rate";
  44. NSString *const FBSDKAppEventNameCompletedTutorial = @"fb_mobile_tutorial_completion";
  45. NSString *const FBSDKAppEventParameterLaunchSource = @"fb_mobile_launch_source";
  46. // Ecommerce related
  47. NSString *const FBSDKAppEventNameAddedToCart = @"fb_mobile_add_to_cart";
  48. NSString *const FBSDKAppEventNameAddedToWishlist = @"fb_mobile_add_to_wishlist";
  49. NSString *const FBSDKAppEventNameInitiatedCheckout = @"fb_mobile_initiated_checkout";
  50. NSString *const FBSDKAppEventNameAddedPaymentInfo = @"fb_mobile_add_payment_info";
  51. // Gaming related
  52. NSString *const FBSDKAppEventNameAchievedLevel = @"fb_mobile_level_achieved";
  53. NSString *const FBSDKAppEventNameUnlockedAchievement = @"fb_mobile_achievement_unlocked";
  54. NSString *const FBSDKAppEventNameSpentCredits = @"fb_mobile_spent_credits";
  55. //
  56. // Public event parameter names
  57. //
  58. NSString *const FBSDKAppEventParameterNameCurrency = @"fb_currency";
  59. NSString *const FBSDKAppEventParameterNameRegistrationMethod = @"fb_registration_method";
  60. NSString *const FBSDKAppEventParameterNameContentType = @"fb_content_type";
  61. NSString *const FBSDKAppEventParameterNameContentID = @"fb_content_id";
  62. NSString *const FBSDKAppEventParameterNameSearchString = @"fb_search_string";
  63. NSString *const FBSDKAppEventParameterNameSuccess = @"fb_success";
  64. NSString *const FBSDKAppEventParameterNameMaxRatingValue = @"fb_max_rating_value";
  65. NSString *const FBSDKAppEventParameterNamePaymentInfoAvailable = @"fb_payment_info_available";
  66. NSString *const FBSDKAppEventParameterNameNumItems = @"fb_num_items";
  67. NSString *const FBSDKAppEventParameterNameLevel = @"fb_level";
  68. NSString *const FBSDKAppEventParameterNameDescription = @"fb_description";
  69. //
  70. // Public event parameter values
  71. //
  72. NSString *const FBSDKAppEventParameterValueNo = @"0";
  73. NSString *const FBSDKAppEventParameterValueYes = @"1";
  74. //
  75. // Event names internal to this file
  76. //
  77. NSString *const FBSDKAppEventNamePurchased = @"fb_mobile_purchase";
  78. NSString *const FBSDKAppEventNameLoginViewUsage = @"fb_login_view_usage";
  79. NSString *const FBSDKAppEventNameShareSheetLaunch = @"fb_share_sheet_launch";
  80. NSString *const FBSDKAppEventNameShareSheetDismiss = @"fb_share_sheet_dismiss";
  81. NSString *const FBSDKAppEventNamePermissionsUILaunch = @"fb_permissions_ui_launch";
  82. NSString *const FBSDKAppEventNamePermissionsUIDismiss = @"fb_permissions_ui_dismiss";
  83. NSString *const FBSDKAppEventNameFBDialogsPresentShareDialog = @"fb_dialogs_present_share";
  84. NSString *const FBSDKAppEventNameFBDialogsPresentShareDialogPhoto = @"fb_dialogs_present_share_photo";
  85. NSString *const FBSDKAppEventNameFBDialogsPresentShareDialogOG = @"fb_dialogs_present_share_og";
  86. NSString *const FBSDKAppEventNameFBDialogsPresentLikeDialogOG = @"fb_dialogs_present_like_og";
  87. NSString *const FBSDKAppEventNameFBDialogsPresentMessageDialog = @"fb_dialogs_present_message";
  88. NSString *const FBSDKAppEventNameFBDialogsPresentMessageDialogPhoto = @"fb_dialogs_present_message_photo";
  89. NSString *const FBSDKAppEventNameFBDialogsPresentMessageDialogOG = @"fb_dialogs_present_message_og";
  90. NSString *const FBSDKAppEventNameFBDialogsNativeLoginDialogStart = @"fb_dialogs_native_login_dialog_start";
  91. NSString *const FBSDKAppEventsNativeLoginDialogStartTime = @"fb_native_login_dialog_start_time";
  92. NSString *const FBSDKAppEventNameFBDialogsNativeLoginDialogEnd = @"fb_dialogs_native_login_dialog_end";
  93. NSString *const FBSDKAppEventsNativeLoginDialogEndTime = @"fb_native_login_dialog_end_time";
  94. NSString *const FBSDKAppEventNameFBDialogsWebLoginCompleted = @"fb_dialogs_web_login_dialog_complete";
  95. NSString *const FBSDKAppEventsWebLoginE2E = @"fb_web_login_e2e";
  96. NSString *const FBSDKAppEventNameFBSessionAuthStart = @"fb_mobile_login_start";
  97. NSString *const FBSDKAppEventNameFBSessionAuthEnd = @"fb_mobile_login_complete";
  98. NSString *const FBSDKAppEventNameFBSessionAuthMethodStart = @"fb_mobile_login_method_start";
  99. NSString *const FBSDKAppEventNameFBSessionAuthMethodEnd = @"fb_mobile_login_method_complete";
  100. NSString *const FBSDKAppEventNameFBSDKLikeButtonImpression = @"fb_like_button_impression";
  101. NSString *const FBSDKAppEventNameFBSDKLoginButtonImpression = @"fb_login_button_impression";
  102. NSString *const FBSDKAppEventNameFBSDKSendButtonImpression = @"fb_send_button_impression";
  103. NSString *const FBSDKAppEventNameFBSDKShareButtonImpression = @"fb_share_button_impression";
  104. NSString *const FBSDKAppEventNameFBSDKLikeButtonDidTap = @"fb_like_button_did_tap";
  105. NSString *const FBSDKAppEventNameFBSDKLoginButtonDidTap = @"fb_login_button_did_tap";
  106. NSString *const FBSDKAppEventNameFBSDKSendButtonDidTap = @"fb_send_button_did_tap";
  107. NSString *const FBSDKAppEventNameFBSDKShareButtonDidTap = @"fb_share_button_did_tap";
  108. NSString *const FBSDKAppEventNameFBSDKLikeControlDidDisable = @"fb_like_control_did_disable";
  109. NSString *const FBSDKAppEventNameFBSDKLikeControlDidLike = @"fb_like_control_did_like";
  110. NSString *const FBSDKAppEventNameFBSDKLikeControlDidPresentDialog = @"fb_like_control_did_present_dialog";
  111. NSString *const FBSDKAppEventNameFBSDKLikeControlDidTap = @"fb_like_control_did_tap";
  112. NSString *const FBSDKAppEventNameFBSDKLikeControlDidUnlike = @"fb_like_control_did_unlike";
  113. NSString *const FBSDKAppEventNameFBSDKLikeControlError = @"fb_like_control_error";
  114. NSString *const FBSDKAppEventNameFBSDKLikeControlImpression = @"fb_like_control_impression";
  115. NSString *const FBSDKAppEventNameFBSDKLikeControlNetworkUnavailable = @"fb_like_control_network_unavailable";
  116. NSString *const FBSDLAppEventNameFBSDKEventShareDialogResult = @"fb_dialog_share_result";
  117. NSString *const FBSDKAppEventNameFBSDKEventMessengerShareDialogResult = @"fb_messenger_dialog_share_result";
  118. NSString *const FBSDKAppEventNameFBSDKEventAppInviteShareDialogResult = @"fb_app_invite_dialog_share_result";
  119. NSString *const FBSDKAppEventNameFBSDKEventShareDialogShow = @"fb_dialog_share_show";
  120. NSString *const FBSDKAppEventNameFBSDKEventMessengerShareDialogShow = @"fb_messenger_dialog_share_show";
  121. NSString *const FBSDKAppEventNameFBSDKEventAppInviteShareDialogShow = @"fb_app_invite_share_show";
  122. // Event Parameters internal to this file
  123. NSString *const FBSDKAppEventParameterDialogOutcome = @"fb_dialog_outcome";
  124. NSString *const FBSDKAppEventParameterDialogErrorMessage = @"fb_dialog_outcome_error_message";
  125. NSString *const FBSDKAppEventParameterDialogMode = @"fb_dialog_mode";
  126. NSString *const FBSDKAppEventParameterDialogShareContentType = @"fb_dialog_share_content_type";
  127. NSString *const FBSDKAppEventParameterLogTime = @"_logTime";
  128. NSString *const FBSDKAppEventParameterEventName = @"_eventName";
  129. NSString *const FBSDKAppEventParameterImplicitlyLogged = @"_implicitlyLogged";
  130. // Event parameter values internal to this file
  131. NSString *const FBSDKAppEventsDialogOutcomeValue_Completed = @"Completed";
  132. NSString *const FBSDKAppEventsDialogOutcomeValue_Cancelled = @"Cancelled";
  133. NSString *const FBSDKAppEventsDialogOutcomeValue_Failed = @"Failed";
  134. NSString *const FBSDKAppEventsDialogShareModeAutomatic = @"Automatic";
  135. NSString *const FBSDKAppEventsDialogShareModeBrowser = @"Browser";
  136. NSString *const FBSDKAppEventsDialogShareModeNative = @"Native";
  137. NSString *const FBSDKAppEventsDialogShareModeShareSheet = @"ShareSheet";
  138. NSString *const FBSDKAppEventsDialogShareModeWeb = @"Web";
  139. NSString *const FBSDKAppEventsDialogShareModeFeedBrowser = @"FeedBrowser";
  140. NSString *const FBSDKAppEventsDialogShareModeFeedWeb = @"FeedWeb";
  141. NSString *const FBSDKAppEventsDialogShareModeUnknown = @"Unknown";
  142. NSString *const FBSDKAppEventsDialogShareContentTypeOpenGraph = @"OpenGraph";
  143. NSString *const FBSDKAppEventsDialogShareContentTypeStatus = @"Status";
  144. NSString *const FBSDKAppEventsDialogShareContentTypePhoto = @"Photo";
  145. NSString *const FBSDKAppEventsDialogShareContentTypeVideo = @"Video";
  146. NSString *const FBSDKAppEventsDialogShareContentTypeUnknown = @"Unknown";
  147. NSString *const FBSDKAppEventsLoggingResultNotification = @"com.facebook.sdk:FBSDKAppEventsLoggingResultNotification";
  148. NSString *const FBSDKAppEventsOverrideAppIDBundleKey = @"FacebookLoggingOverrideAppID";
  149. //
  150. // Push Notifications
  151. // Activities Endpoint Parameter
  152. static NSString *const FBSDKActivitesParameterPushDeviceToken = @"device_token";
  153. // Event Name
  154. static NSString *const FBSDKAppEventNamePushOpened = @"fb_mobile_push_opened";
  155. // Event Parameter
  156. static NSString *const FBSDKAppEventParameterPushCampaign = @"fb_push_campaign";
  157. static NSString *const FBSDKAppEventParameterPushAction = @"fb_push_action";
  158. // Payload Keys
  159. static NSString *const FBSDKAppEventsPushPayloadKey = @"fb_push_payload";
  160. static NSString *const FBSDKAppEventsPushPayloadCampaignKey = @"campaign";
  161. #define NUM_LOG_EVENTS_TO_TRY_TO_FLUSH_AFTER 100
  162. #define FLUSH_PERIOD_IN_SECONDS 15
  163. #define APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD 60 * 60 * 24
  164. static NSString *g_overrideAppID = nil;
  165. @interface FBSDKAppEvents ()
  166. @property (nonatomic, readwrite) FBSDKAppEventsFlushBehavior flushBehavior;
  167. //for testing only.
  168. @property (nonatomic, assign) BOOL disableTimer;
  169. @property (nonatomic, copy) NSString *pushNotificationsDeviceTokenString;
  170. @end
  171. @implementation FBSDKAppEvents
  172. {
  173. BOOL _explicitEventsLoggedYet;
  174. NSTimer *_flushTimer;
  175. NSTimer *_attributionIDRecheckTimer;
  176. FBSDKServerConfiguration *_serverConfiguration;
  177. FBSDKAppEventsState *_appEventsState;
  178. }
  179. #pragma mark - Object Lifecycle
  180. + (void)initialize
  181. {
  182. if (self == [FBSDKAppEvents class]) {
  183. g_overrideAppID = [[[NSBundle mainBundle] objectForInfoDictionaryKey:FBSDKAppEventsOverrideAppIDBundleKey] copy];
  184. }
  185. }
  186. - (FBSDKAppEvents *)init
  187. {
  188. self = [super init];
  189. if (self) {
  190. _flushBehavior = FBSDKAppEventsFlushBehaviorAuto;
  191. _flushTimer = [NSTimer timerWithTimeInterval:FLUSH_PERIOD_IN_SECONDS
  192. target:self
  193. selector:@selector(flushTimerFired:)
  194. userInfo:nil
  195. repeats:YES];
  196. _attributionIDRecheckTimer = [NSTimer timerWithTimeInterval:APP_SUPPORTS_ATTRIBUTION_ID_RECHECK_PERIOD
  197. target:self
  198. selector:@selector(appSettingsFetchStateResetTimerFired:)
  199. userInfo:nil
  200. repeats:YES];
  201. [[NSRunLoop mainRunLoop] addTimer:_flushTimer forMode:NSDefaultRunLoopMode];
  202. [[NSRunLoop mainRunLoop] addTimer:_attributionIDRecheckTimer forMode:NSDefaultRunLoopMode];
  203. [[NSNotificationCenter defaultCenter]
  204. addObserver:self
  205. selector:@selector(applicationMovingFromActiveStateOrTerminating)
  206. name:UIApplicationWillResignActiveNotification
  207. object:NULL];
  208. [[NSNotificationCenter defaultCenter]
  209. addObserver:self
  210. selector:@selector(applicationMovingFromActiveStateOrTerminating)
  211. name:UIApplicationWillTerminateNotification
  212. object:NULL];
  213. [[NSNotificationCenter defaultCenter]
  214. addObserver:self
  215. selector:@selector(applicationDidBecomeActive)
  216. name:UIApplicationDidBecomeActiveNotification
  217. object:NULL];
  218. }
  219. return self;
  220. }
  221. - (void)dealloc
  222. {
  223. [[NSNotificationCenter defaultCenter] removeObserver:self];
  224. // technically these timers retain self so there's a cycle but
  225. // we're a singleton anyway.
  226. [_flushTimer invalidate];
  227. [_attributionIDRecheckTimer invalidate];
  228. }
  229. #pragma mark - Public Methods
  230. + (void)logEvent:(NSString *)eventName
  231. {
  232. [FBSDKAppEvents logEvent:eventName
  233. parameters:nil];
  234. }
  235. + (void)logEvent:(NSString *)eventName
  236. valueToSum:(double)valueToSum
  237. {
  238. [FBSDKAppEvents logEvent:eventName
  239. valueToSum:valueToSum
  240. parameters:nil];
  241. }
  242. + (void)logEvent:(NSString *)eventName
  243. parameters:(NSDictionary *)parameters
  244. {
  245. [FBSDKAppEvents logEvent:eventName
  246. valueToSum:nil
  247. parameters:parameters
  248. accessToken:nil];
  249. }
  250. + (void)logEvent:(NSString *)eventName
  251. valueToSum:(double)valueToSum
  252. parameters:(NSDictionary *)parameters
  253. {
  254. [FBSDKAppEvents logEvent:eventName
  255. valueToSum:[NSNumber numberWithDouble:valueToSum]
  256. parameters:parameters
  257. accessToken:nil];
  258. }
  259. + (void)logEvent:(NSString *)eventName
  260. valueToSum:(NSNumber *)valueToSum
  261. parameters:(NSDictionary *)parameters
  262. accessToken:(FBSDKAccessToken *)accessToken
  263. {
  264. [[FBSDKAppEvents singleton] instanceLogEvent:eventName
  265. valueToSum:valueToSum
  266. parameters:parameters
  267. isImplicitlyLogged:NO
  268. accessToken:accessToken];
  269. }
  270. + (void)logPurchase:(double)purchaseAmount
  271. currency:(NSString *)currency
  272. {
  273. [FBSDKAppEvents logPurchase:purchaseAmount
  274. currency:currency
  275. parameters:nil];
  276. }
  277. + (void)logPurchase:(double)purchaseAmount
  278. currency:(NSString *)currency
  279. parameters:(NSDictionary *)parameters
  280. {
  281. [FBSDKAppEvents logPurchase:purchaseAmount
  282. currency:currency
  283. parameters:parameters
  284. accessToken:nil];
  285. }
  286. + (void)logPurchase:(double)purchaseAmount
  287. currency:(NSString *)currency
  288. parameters:(NSDictionary *)parameters
  289. accessToken:(FBSDKAccessToken *)accessToken
  290. {
  291. // A purchase event is just a regular logged event with a given event name
  292. // and treating the currency value as going into the parameters dictionary.
  293. NSDictionary *newParameters;
  294. if (!parameters) {
  295. newParameters = @{ FBSDKAppEventParameterNameCurrency : currency };
  296. } else {
  297. newParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
  298. [newParameters setValue:currency forKey:FBSDKAppEventParameterNameCurrency];
  299. }
  300. [FBSDKAppEvents logEvent:FBSDKAppEventNamePurchased
  301. valueToSum:[NSNumber numberWithDouble:purchaseAmount]
  302. parameters:newParameters
  303. accessToken:accessToken];
  304. // Unless the behavior is set to only allow explicit flushing, we go ahead and flush, since purchase events
  305. // are relatively rare and relatively high value and worth getting across on wire right away.
  306. if ([FBSDKAppEvents flushBehavior] != FBSDKAppEventsFlushBehaviorExplicitOnly) {
  307. [[FBSDKAppEvents singleton] flushForReason:FBSDKAppEventsFlushReasonEagerlyFlushingEvent];
  308. }
  309. }
  310. /*
  311. * Push Notifications Logging
  312. */
  313. + (void)logPushNotificationOpen:(NSDictionary *)payload
  314. {
  315. [self logPushNotificationOpen:payload action:nil];
  316. }
  317. + (void)logPushNotificationOpen:(NSDictionary *)payload action:(NSString *)action
  318. {
  319. NSDictionary *facebookPayload = payload[FBSDKAppEventsPushPayloadKey];
  320. if (!facebookPayload) {
  321. return;
  322. }
  323. NSString *campaign = facebookPayload[FBSDKAppEventsPushPayloadCampaignKey];
  324. if (campaign.length == 0) {
  325. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
  326. logEntry:@"Malformed payload specified for logging a push notification open."];
  327. return;
  328. }
  329. NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithObject:campaign forKey:FBSDKAppEventParameterPushCampaign];
  330. if (action) {
  331. parameters[FBSDKAppEventParameterPushAction] = action;
  332. }
  333. [self logEvent:FBSDKAppEventNamePushOpened parameters:parameters];
  334. }
  335. + (void)activateApp
  336. {
  337. [FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass(self)];
  338. // Fetch app settings and register for transaction notifications only if app supports implicit purchase
  339. // events
  340. FBSDKAppEvents *instance = [FBSDKAppEvents singleton];
  341. [instance publishInstall];
  342. [instance fetchServerConfiguration:NULL];
  343. // Restore time spent data, indicating that we're being called from "activateApp", which will,
  344. // when appropriate, result in logging an "activated app" and "deactivated app" (for the
  345. // previous session) App Event.
  346. [FBSDKTimeSpentData restore:YES];
  347. }
  348. + (void)setPushNotificationsDeviceToken:(NSData *)deviceToken
  349. {
  350. [FBSDKAppEvents singleton].pushNotificationsDeviceTokenString = [FBSDKInternalUtility hexadecimalStringFromData:deviceToken];
  351. }
  352. + (FBSDKAppEventsFlushBehavior)flushBehavior
  353. {
  354. return [FBSDKAppEvents singleton].flushBehavior;
  355. }
  356. + (void)setFlushBehavior:(FBSDKAppEventsFlushBehavior)flushBehavior
  357. {
  358. [FBSDKAppEvents singleton].flushBehavior = flushBehavior;
  359. }
  360. + (NSString *)loggingOverrideAppID
  361. {
  362. return g_overrideAppID;
  363. }
  364. + (void)setLoggingOverrideAppID:(NSString *)appID
  365. {
  366. if (![g_overrideAppID isEqualToString:appID]) {
  367. FBSDKConditionalLog(![FBSDKAppEvents singleton]->_explicitEventsLoggedYet,
  368. FBSDKLoggingBehaviorDeveloperErrors,
  369. @"[FBSDKAppEvents setLoggingOverrideAppID:] should only be called prior to any events being logged.");
  370. g_overrideAppID = appID;
  371. }
  372. }
  373. + (void)flush
  374. {
  375. [[FBSDKAppEvents singleton] flushForReason:FBSDKAppEventsFlushReasonExplicit];
  376. }
  377. #pragma mark - Internal Methods
  378. + (void)logImplicitEvent:(NSString *)eventName
  379. valueToSum:(NSNumber *)valueToSum
  380. parameters:(NSDictionary *)parameters
  381. accessToken:(FBSDKAccessToken *)accessToken
  382. {
  383. [[FBSDKAppEvents singleton] instanceLogEvent:eventName
  384. valueToSum:valueToSum
  385. parameters:parameters
  386. isImplicitlyLogged:YES
  387. accessToken:accessToken];
  388. }
  389. + (FBSDKAppEvents *)singleton
  390. {
  391. static dispatch_once_t pred;
  392. static FBSDKAppEvents *shared = nil;
  393. dispatch_once(&pred, ^{
  394. shared = [[FBSDKAppEvents alloc] init];
  395. });
  396. return shared;
  397. }
  398. - (void)flushForReason:(FBSDKAppEventsFlushReason)flushReason
  399. {
  400. // Always flush asynchronously, even on main thread, for two reasons:
  401. // - most consistent code path for all threads.
  402. // - allow locks being held by caller to be released prior to actual flushing work being done.
  403. @synchronized (self) {
  404. if (!_appEventsState) {
  405. return;
  406. }
  407. FBSDKAppEventsState *copy = [_appEventsState copy];
  408. _appEventsState = [[FBSDKAppEventsState alloc] initWithToken:copy.tokenString
  409. appID:copy.appID];
  410. dispatch_async(dispatch_get_main_queue(), ^{
  411. [self flushOnMainQueue:copy forReason:flushReason];
  412. });
  413. }
  414. }
  415. #pragma mark - Private Methods
  416. - (NSString *)appID
  417. {
  418. return [FBSDKAppEvents loggingOverrideAppID] ?: [FBSDKSettings appID];
  419. }
  420. - (void)publishInstall
  421. {
  422. NSString *appID = [self appID];
  423. NSString *lastAttributionPingString = [NSString stringWithFormat:@"com.facebook.sdk:lastAttributionPing%@", appID];
  424. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  425. if ([defaults objectForKey:lastAttributionPingString]) {
  426. return;
  427. }
  428. [self fetchServerConfiguration:^{
  429. NSDictionary *params = [FBSDKAppEventsUtility activityParametersDictionaryForEvent:@"MOBILE_APP_INSTALL"
  430. implicitEventsOnly:NO
  431. shouldAccessAdvertisingID:_serverConfiguration.isAdvertisingIDEnabled];
  432. NSString *path = [NSString stringWithFormat:@"%@/activities", appID];
  433. FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:path
  434. parameters:params
  435. tokenString:nil
  436. HTTPMethod:@"POST"
  437. flags:FBSDKGraphRequestFlagDoNotInvalidateTokenOnError | FBSDKGraphRequestFlagDisableErrorRecovery];
  438. [request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
  439. if (!error) {
  440. [defaults setObject:[NSDate date] forKey:lastAttributionPingString];
  441. NSString *lastInstallResponseKey = [NSString stringWithFormat:@"com.facebook.sdk:lastInstallResponse%@", appID];
  442. [defaults setObject:result forKey:lastInstallResponseKey];
  443. [defaults synchronize];
  444. }
  445. }];
  446. }];
  447. }
  448. // app events can use a server configuration up to 24 hours old to minimize network traffic.
  449. - (void)fetchServerConfiguration:(void (^)(void))callback
  450. {
  451. if (_serverConfiguration == nil) {
  452. [FBSDKServerConfigurationManager loadServerConfigurationWithCompletionBlock:^(FBSDKServerConfiguration *serverConfiguration, NSError *error) {
  453. _serverConfiguration = serverConfiguration;
  454. if (_serverConfiguration.implicitPurchaseLoggingEnabled) {
  455. [FBSDKPaymentObserver startObservingTransactions];
  456. } else {
  457. [FBSDKPaymentObserver stopObservingTransactions];
  458. }
  459. if (callback) {
  460. callback();
  461. }
  462. }];
  463. return;
  464. }
  465. if (callback) {
  466. callback();
  467. }
  468. }
  469. - (void)instanceLogEvent:(NSString *)eventName
  470. valueToSum:(NSNumber *)valueToSum
  471. parameters:(NSDictionary *)parameters
  472. isImplicitlyLogged:(BOOL)isImplicitlyLogged
  473. accessToken:(FBSDKAccessToken *)accessToken
  474. {
  475. if (isImplicitlyLogged && _serverConfiguration && !_serverConfiguration.isImplicitLoggingSupported) {
  476. return;
  477. }
  478. if (!isImplicitlyLogged && !_explicitEventsLoggedYet) {
  479. _explicitEventsLoggedYet = YES;
  480. }
  481. __block BOOL failed = NO;
  482. if (![FBSDKAppEventsUtility validateIdentifier:eventName]) {
  483. failed = YES;
  484. }
  485. // Make sure parameter dictionary is well formed. Log and exit if not.
  486. [parameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  487. if (![key isKindOfClass:[NSString class]]) {
  488. [FBSDKAppEventsUtility logAndNotify:[NSString stringWithFormat:@"The keys in the parameters must be NSStrings, '%@' is not.", key]];
  489. failed = YES;
  490. }
  491. if (![FBSDKAppEventsUtility validateIdentifier:key]) {
  492. failed = YES;
  493. }
  494. if (![obj isKindOfClass:[NSString class]] && ![obj isKindOfClass:[NSNumber class]]) {
  495. [FBSDKAppEventsUtility logAndNotify:[NSString stringWithFormat:@"The values in the parameters dictionary must be NSStrings or NSNumbers, '%@' is not.", obj]];
  496. failed = YES;
  497. }
  498. }
  499. ];
  500. if (failed) {
  501. return;
  502. }
  503. NSMutableDictionary *eventDictionary = [NSMutableDictionary dictionaryWithDictionary:parameters];
  504. eventDictionary[FBSDKAppEventParameterEventName] = eventName;
  505. if (!eventDictionary[FBSDKAppEventParameterLogTime]) {
  506. eventDictionary[FBSDKAppEventParameterLogTime] = @([FBSDKAppEventsUtility unixTimeNow]);
  507. }
  508. [FBSDKInternalUtility dictionary:eventDictionary setObject:valueToSum forKey:@"_valueToSum"];
  509. if (isImplicitlyLogged) {
  510. eventDictionary[FBSDKAppEventParameterImplicitlyLogged] = @"1";
  511. }
  512. NSString *currentViewControllerName;
  513. if ([NSThread isMainThread]) {
  514. // We only collect the view controller when on the main thread, as the behavior off
  515. // the main thread is unpredictable. Besides, UI state for off-main-thread computations
  516. // isn't really relevant anyhow.
  517. UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController;
  518. if (vc) {
  519. currentViewControllerName = [[vc class] description];
  520. } else {
  521. currentViewControllerName = @"no_ui";
  522. }
  523. } else {
  524. currentViewControllerName = @"off_thread";
  525. }
  526. eventDictionary[@"_ui"] = currentViewControllerName;
  527. NSString *tokenString = [FBSDKAppEventsUtility tokenStringToUseFor:accessToken];
  528. NSString *appID = [self appID];
  529. @synchronized (self) {
  530. if (!_appEventsState) {
  531. _appEventsState = [[FBSDKAppEventsState alloc] initWithToken:tokenString appID:appID];
  532. } else if (![_appEventsState isCompatibleWithTokenString:tokenString appID:appID]) {
  533. if (self.flushBehavior == FBSDKAppEventsFlushBehaviorExplicitOnly) {
  534. [FBSDKAppEventsStateManager persistAppEventsData:_appEventsState];
  535. } else {
  536. [self flushForReason:FBSDKAppEventsFlushReasonSessionChange];
  537. }
  538. _appEventsState = [[FBSDKAppEventsState alloc] initWithToken:tokenString appID:appID];
  539. }
  540. [_appEventsState addEvent:eventDictionary isImplicit:isImplicitlyLogged];
  541. if (!isImplicitlyLogged) {
  542. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
  543. formatString:@"FBSDKAppEvents: Recording event @ %ld: %@",
  544. [FBSDKAppEventsUtility unixTimeNow],
  545. eventDictionary];
  546. }
  547. [self checkPersistedEvents];
  548. if (_appEventsState.events.count > NUM_LOG_EVENTS_TO_TRY_TO_FLUSH_AFTER &&
  549. self.flushBehavior != FBSDKAppEventsFlushBehaviorExplicitOnly) {
  550. [self flushForReason:FBSDKAppEventsFlushReasonEventThreshold];
  551. }
  552. }
  553. }
  554. // this fetches persisted event states.
  555. // for those matching the currently tracked events, add it.
  556. // otherwise, either flush (if not explicitonly behavior) or persist them back.
  557. - (void)checkPersistedEvents
  558. {
  559. NSArray *existingEventsStates = [FBSDKAppEventsStateManager retrievePersistedAppEventsStates];
  560. if (existingEventsStates.count == 0) {
  561. return;
  562. }
  563. FBSDKAppEventsState *matchingEventsPreviouslySaved = nil;
  564. // reduce lock time by creating a new FBSDKAppEventsState to collect matching persisted events.
  565. @synchronized(self) {
  566. if (_appEventsState) {
  567. matchingEventsPreviouslySaved = [[FBSDKAppEventsState alloc] initWithToken:_appEventsState.tokenString
  568. appID:_appEventsState.appID];
  569. }
  570. }
  571. for (FBSDKAppEventsState *saved in existingEventsStates) {
  572. if ([saved isCompatibleWithAppEventsState:matchingEventsPreviouslySaved]) {
  573. [matchingEventsPreviouslySaved addEventsFromAppEventState:saved];
  574. } else {
  575. if (self.flushBehavior == FBSDKAppEventsFlushBehaviorExplicitOnly) {
  576. [FBSDKAppEventsStateManager persistAppEventsData:saved];
  577. } else {
  578. dispatch_async(dispatch_get_main_queue(), ^{
  579. [self flushOnMainQueue:saved forReason:FBSDKAppEventsFlushReasonPersistedEvents];
  580. });
  581. }
  582. }
  583. }
  584. if (matchingEventsPreviouslySaved.events.count > 0) {
  585. @synchronized(self) {
  586. if ([_appEventsState isCompatibleWithAppEventsState:matchingEventsPreviouslySaved]) {
  587. [_appEventsState addEventsFromAppEventState:matchingEventsPreviouslySaved];
  588. }
  589. }
  590. }
  591. }
  592. - (void)flushOnMainQueue:(FBSDKAppEventsState *)appEventsState
  593. forReason:(FBSDKAppEventsFlushReason)reason
  594. {
  595. if (appEventsState.events.count == 0) {
  596. return;
  597. }
  598. [FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];
  599. [self fetchServerConfiguration:^(void) {
  600. NSString *JSONString = [appEventsState JSONStringForEvents:_serverConfiguration.implicitLoggingEnabled];
  601. NSData *encodedEvents = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
  602. if (!encodedEvents) {
  603. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
  604. logEntry:@"FBSDKAppEvents: Flushing skipped - no events after removing implicitly logged ones.\n"];
  605. return;
  606. }
  607. NSMutableDictionary *postParameters = [FBSDKAppEventsUtility
  608. activityParametersDictionaryForEvent:@"CUSTOM_APP_EVENTS"
  609. implicitEventsOnly:appEventsState.areAllEventsImplicit
  610. shouldAccessAdvertisingID:_serverConfiguration.advertisingIDEnabled];
  611. postParameters[@"custom_events_file"] = encodedEvents;
  612. if (appEventsState.numSkipped > 0) {
  613. postParameters[@"num_skipped_events"] = [NSString stringWithFormat:@"%lu", (unsigned long)appEventsState.numSkipped];
  614. }
  615. if (self.pushNotificationsDeviceTokenString) {
  616. postParameters[FBSDKActivitesParameterPushDeviceToken] = self.pushNotificationsDeviceTokenString;
  617. }
  618. NSString *loggingEntry = nil;
  619. if ([[FBSDKSettings loggingBehavior] containsObject:FBSDKLoggingBehaviorAppEvents]) {
  620. NSData *prettyJSONData = [NSJSONSerialization dataWithJSONObject:appEventsState.events
  621. options:NSJSONWritingPrettyPrinted
  622. error:NULL];
  623. NSString *prettyPrintedJsonEvents = [[NSString alloc] initWithData:prettyJSONData
  624. encoding:NSUTF8StringEncoding];
  625. // Remove this param -- just an encoding of the events which we pretty print later.
  626. NSMutableDictionary *paramsForPrinting = [postParameters mutableCopy];
  627. [paramsForPrinting removeObjectForKey:@"custom_events_file"];
  628. loggingEntry = [NSString stringWithFormat:@"FBSDKAppEvents: Flushed @ %ld, %lu events due to '%@' - %@\nEvents: %@",
  629. [FBSDKAppEventsUtility unixTimeNow],
  630. (unsigned long)appEventsState.events.count,
  631. [FBSDKAppEventsUtility flushReasonToString:reason],
  632. paramsForPrinting,
  633. prettyPrintedJsonEvents];
  634. }
  635. FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:[NSString stringWithFormat:@"%@/activities", appEventsState.appID]
  636. parameters:postParameters
  637. tokenString:appEventsState.tokenString
  638. HTTPMethod:@"POST"
  639. flags:FBSDKGraphRequestFlagDoNotInvalidateTokenOnError | FBSDKGraphRequestFlagDisableErrorRecovery];
  640. [request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
  641. [self handleActivitiesPostCompletion:error
  642. loggingEntry:loggingEntry
  643. appEventsState:(FBSDKAppEventsState *)appEventsState];
  644. }];
  645. }];
  646. }
  647. - (void)handleActivitiesPostCompletion:(NSError *)error
  648. loggingEntry:(NSString *)loggingEntry
  649. appEventsState:(FBSDKAppEventsState *)appEventsState
  650. {
  651. typedef NS_ENUM(NSUInteger, FBSDKAppEventsFlushResult) {
  652. FlushResultSuccess,
  653. FlushResultServerError,
  654. FlushResultNoConnectivity
  655. };
  656. [FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];
  657. FBSDKAppEventsFlushResult flushResult = FlushResultSuccess;
  658. if (error) {
  659. NSInteger errorCode = [error.userInfo[FBSDKGraphRequestErrorHTTPStatusCodeKey] integerValue];
  660. // We interpret a 400 coming back from FBRequestConnection as a server error due to improper data being
  661. // sent down. Otherwise we assume no connectivity, or another condition where we could treat it as no connectivity.
  662. flushResult = errorCode == 400 ? FlushResultServerError : FlushResultNoConnectivity;
  663. }
  664. if (flushResult == FlushResultServerError) {
  665. // Only log events that developer can do something with (i.e., if parameters are incorrect).
  666. // as opposed to cases where the token is bad.
  667. if ([error.userInfo[FBSDKGraphRequestErrorCategoryKey] unsignedIntegerValue] == FBSDKGraphRequestErrorCategoryOther) {
  668. NSString *message = [NSString stringWithFormat:@"Failed to send AppEvents: %@", error];
  669. [FBSDKAppEventsUtility logAndNotify:message allowLogAsDeveloperError:!appEventsState.areAllEventsImplicit];
  670. }
  671. } else if (flushResult == FlushResultNoConnectivity) {
  672. @synchronized(self) {
  673. if ([appEventsState isCompatibleWithAppEventsState:_appEventsState]) {
  674. [_appEventsState addEventsFromAppEventState:appEventsState];
  675. } else {
  676. // flush failed due to connectivity. Persist to be tried again later.
  677. [FBSDKAppEventsStateManager persistAppEventsData:appEventsState];
  678. }
  679. }
  680. }
  681. NSString *resultString = @"<unknown>";
  682. switch (flushResult) {
  683. case FlushResultSuccess:
  684. resultString = @"Success";
  685. break;
  686. case FlushResultNoConnectivity:
  687. resultString = @"No Connectivity";
  688. break;
  689. case FlushResultServerError:
  690. resultString = [NSString stringWithFormat:@"Server Error - %@", [error description]];
  691. break;
  692. }
  693. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
  694. formatString:@"%@\nFlush Result : %@", loggingEntry, resultString];
  695. }
  696. - (void)flushTimerFired:(id)arg
  697. {
  698. [FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];
  699. if (self.flushBehavior != FBSDKAppEventsFlushBehaviorExplicitOnly && !self.disableTimer) {
  700. [self flushForReason:FBSDKAppEventsFlushReasonTimer];
  701. }
  702. }
  703. - (void)appSettingsFetchStateResetTimerFired:(id)arg
  704. {
  705. _serverConfiguration = nil;
  706. }
  707. - (void)applicationDidBecomeActive
  708. {
  709. [FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];
  710. [self checkPersistedEvents];
  711. // Restore time spent data, indicating that we're not being called from "activateApp".
  712. [FBSDKTimeSpentData restore:NO];
  713. }
  714. - (void)applicationMovingFromActiveStateOrTerminating
  715. {
  716. // When moving from active state, we don't have time to wait for the result of a flush, so
  717. // just persist events to storage, and we'll process them at the next activation.
  718. FBSDKAppEventsState *copy = nil;
  719. @synchronized (self) {
  720. copy = [_appEventsState copy];
  721. _appEventsState = nil;
  722. }
  723. if (copy) {
  724. [FBSDKAppEventsStateManager persistAppEventsData:copy];
  725. }
  726. [FBSDKTimeSpentData suspend];
  727. }
  728. #pragma mark - Custom Audience
  729. + (FBSDKGraphRequest *)requestForCustomAudienceThirdPartyIDWithAccessToken:(FBSDKAccessToken *)accessToken
  730. {
  731. accessToken = accessToken ?: [FBSDKAccessToken currentAccessToken];
  732. // Rules for how we use the attribution ID / advertiser ID for an 'custom_audience_third_party_id' Graph API request
  733. // 1) if the OS tells us that the user has Limited Ad Tracking, then just don't send, and return a nil in the token.
  734. // 2) if the app has set 'limitEventAndDataUsage', this effectively implies that app-initiated ad targeting shouldn't happen,
  735. // so use that data here to return nil as well.
  736. // 3) if we have a user session token, then no need to send attribution ID / advertiser ID back as the udid parameter
  737. // 4) otherwise, send back the udid parameter.
  738. if ([FBSDKAppEventsUtility advertisingTrackingStatus] == FBSDKAdvertisingTrackingDisallowed || [FBSDKSettings limitEventAndDataUsage]) {
  739. return nil;
  740. }
  741. NSString *tokenString = [FBSDKAppEventsUtility tokenStringToUseFor:accessToken];
  742. NSString *udid = nil;
  743. if (!accessToken) {
  744. // We don't have a logged in user, so we need some form of udid representation. Prefer advertiser ID if
  745. // available, and back off to attribution ID if not. Note that this function only makes sense to be
  746. // called in the context of advertising.
  747. udid = [FBSDKAppEventsUtility advertiserID];
  748. if (!udid) {
  749. udid = [FBSDKAppEventsUtility attributionID];
  750. }
  751. if (!udid) {
  752. // No udid, and no user token. No point in making the request.
  753. return nil;
  754. }
  755. }
  756. NSDictionary *parameters = nil;
  757. if (udid) {
  758. parameters = @{ @"udid" : udid };
  759. }
  760. NSString *graphPath = [NSString stringWithFormat:@"%@/custom_audience_third_party_id", [[self singleton] appID]];
  761. FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:graphPath
  762. parameters:parameters
  763. tokenString:tokenString
  764. HTTPMethod:nil
  765. flags:FBSDKGraphRequestFlagDoNotInvalidateTokenOnError | FBSDKGraphRequestFlagDisableErrorRecovery];
  766. return request;
  767. }
  768. @end