pod install facebook sdk

This commit is contained in:
bluesea 2024-04-09 17:20:05 +08:00
parent d89924d5a2
commit 58a8d5f8ce
334 changed files with 79458 additions and 38657 deletions

View File

@ -45,6 +45,6 @@ pod 'FirebaseCrashlytics'
pod 'Firebase/AnalyticsWithoutAdIdSupport'
pod 'FirebaseAuth'
pod 'FirebaseFirestore'
pod 'FacebookCore'
end

View File

@ -823,6 +823,14 @@ PODS:
- BoringSSL-GRPC/Interface (= 0.0.32)
- BoringSSL-GRPC/Interface (0.0.32)
- DeviceKit (5.2.2)
- FacebookCore (0.9.0):
- FBSDKCoreKit (~> 5.0)
- FBSDKCoreKit (5.15.1):
- FBSDKCoreKit/Basics (= 5.15.1)
- FBSDKCoreKit/Core (= 5.15.1)
- FBSDKCoreKit/Basics (5.15.1)
- FBSDKCoreKit/Core (5.15.1):
- FBSDKCoreKit/Basics
- Firebase/AnalyticsWithoutAdIdSupport (10.23.0):
- Firebase/CoreOnly
- FirebaseAnalytics/WithoutAdIdSupport (~> 10.23.0)
@ -1038,6 +1046,7 @@ PODS:
DEPENDENCIES:
- Alamofire
- DeviceKit
- FacebookCore
- Firebase/AnalyticsWithoutAdIdSupport
- FirebaseAuth
- FirebaseCrashlytics
@ -1053,6 +1062,8 @@ SPEC REPOS:
- Alamofire
- BoringSSL-GRPC
- DeviceKit
- FacebookCore
- FBSDKCoreKit
- Firebase
- FirebaseAnalytics
- FirebaseAppCheckInterop
@ -1089,6 +1100,8 @@ SPEC CHECKSUMS:
Alamofire: 3ca42e259043ee0dc5c0cdd76c4bc568b8e42af7
BoringSSL-GRPC: 1e2348957acdbcad360b80a264a90799984b2ba6
DeviceKit: e36aaf2a0d142ef0b4fac2007649a4414af234be
FacebookCore: ba86524b66cfa86d0f8e65d08faa8504a9f732dd
FBSDKCoreKit: 1d5acf7c9d7a2f92bb1a242dc60cae5b7adb91df
Firebase: 333ec7c6b12fa09c77b5162cda6b862102211d50
FirebaseAnalytics: 45f6e2e5ef8ccbb90be73ae983c3b20fa78837f7
FirebaseAppCheckInterop: a1955ce8c30f38f87e7d091630e871e91154d65d
@ -1120,6 +1133,6 @@ SPEC CHECKSUMS:
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
TZImagePickerController: f1c9f1cae6ac0e30b31aaa9698f9bf4a7cf5b84f
PODFILE CHECKSUM: 3d5feb483f1e0625e790607cb3a94eeb8fca4d45
PODFILE CHECKSUM: 348e68c1ad5662db981acd0c0690ba09e3436445
COCOAPODS: 1.15.2

View File

@ -0,0 +1,865 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
#if !TARGET_OS_TV
#import <WebKit/WebKit.h>
#endif
#ifdef BUCK
#import <FBSDKCoreKit/FBSDKGraphRequestConnection.h>
#else
#import "FBSDKGraphRequestConnection.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@class FBSDKAccessToken;
@class FBSDKGraphRequest;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/** NSNotificationCenter name indicating a result of a failed log flush attempt. The posted object will be an NSError instance. */
FOUNDATION_EXPORT NSNotificationName const FBSDKAppEventsLoggingResultNotification
NS_SWIFT_NAME(AppEventsLoggingResult);
#else
/** NSNotificationCenter name indicating a result of a failed log flush attempt. The posted object will be an NSError instance. */
FOUNDATION_EXPORT NSString *const FBSDKAppEventsLoggingResultNotification
NS_SWIFT_NAME(AppEventsLoggingResultNotification);
#endif
/** optional plist key ("FacebookLoggingOverrideAppID") for setting `loggingOverrideAppID` */
FOUNDATION_EXPORT NSString *const FBSDKAppEventsOverrideAppIDBundleKey
NS_SWIFT_NAME(AppEventsOverrideAppIDBundleKey);
/**
NS_ENUM (NSUInteger, FBSDKAppEventsFlushBehavior)
Specifies when `FBSDKAppEvents` sends log events to the server.
*/
typedef NS_ENUM(NSUInteger, FBSDKAppEventsFlushBehavior)
{
/** Flush automatically: periodically (once a minute or every 100 logged events) and always at app reactivation. */
FBSDKAppEventsFlushBehaviorAuto = 0,
/** Only flush when the `flush` method is called. When an app is moved to background/terminated, the
events are persisted and re-established at activation, but they will only be written with an
explicit call to `flush`. */
FBSDKAppEventsFlushBehaviorExplicitOnly,
} NS_SWIFT_NAME(AppEvents.FlushBehavior);
/**
NS_ENUM(NSUInteger, FBSDKProductAvailability)
Specifies product availability for Product Catalog product item update
*/
typedef NS_ENUM(NSUInteger, FBSDKProductAvailability)
{
/**
* Item ships immediately
*/
FBSDKProductAvailabilityInStock = 0,
/**
* No plan to restock
*/
FBSDKProductAvailabilityOutOfStock,
/**
* Available in future
*/
FBSDKProductAvailabilityPreOrder,
/**
* Ships in 1-2 weeks
*/
FBSDKProductAvailabilityAvailableForOrder,
/**
* Discontinued
*/
FBSDKProductAvailabilityDiscontinued,
} NS_SWIFT_NAME(AppEvents.ProductAvailability);
/**
NS_ENUM(NSUInteger, FBSDKProductCondition)
Specifies product condition for Product Catalog product item update
*/
typedef NS_ENUM(NSUInteger, FBSDKProductCondition)
{
FBSDKProductConditionNew = 0,
FBSDKProductConditionRefurbished,
FBSDKProductConditionUsed,
} NS_SWIFT_NAME(AppEvents.ProductCondition);
/**
@methodgroup Predefined event names for logging events common to many apps. Logging occurs through the `logEvent` family of methods on `FBSDKAppEvents`.
Common event parameters are provided in the `FBSDKAppEventsParameterNames*` constants.
*/
/// typedef for FBSDKAppEventName
typedef NSString *const FBSDKAppEventName NS_TYPED_EXTENSIBLE_ENUM NS_SWIFT_NAME(AppEvents.Name);
/** Log this event when the user has achieved a level in the app. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameAchievedLevel;
/** Log this event when the user has entered their payment info. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameAddedPaymentInfo;
/** Log this event when the user has added an item to their cart. The valueToSum passed to logEvent should be the item's price. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameAddedToCart;
/** Log this event when the user has added an item to their wishlist. The valueToSum passed to logEvent should be the item's price. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameAddedToWishlist;
/** Log this event when a user has completed registration with the app. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameCompletedRegistration;
/** Log this event when the user has completed a tutorial in the app. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameCompletedTutorial;
/** Log this event when the user has entered the checkout process. The valueToSum passed to logEvent should be the total price in the cart. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameInitiatedCheckout;
/** Log this event when the user has completed a transaction. The valueToSum passed to logEvent should be the total price of the transaction. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNamePurchased;
/** Log this event when the user has rated an item in the app. The valueToSum passed to logEvent should be the numeric rating. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameRated;
/** Log this event when a user has performed a search within the app. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameSearched;
/** Log this event when the user has spent app credits. The valueToSum passed to logEvent should be the number of credits spent. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameSpentCredits;
/** Log this event when the user has unlocked an achievement in the app. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameUnlockedAchievement;
/** Log this event when a user has viewed a form of content in the app. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameViewedContent;
/** A telephone/SMS, email, chat or other type of contact between a customer and your business. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameContact;
/** The customization of products through a configuration tool or other application your business owns. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameCustomizeProduct;
/** The donation of funds to your organization or cause. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameDonate;
/** When a person finds one of your locations via web or application, with an intention to visit (example: find product at a local store). */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameFindLocation;
/** The booking of an appointment to visit one of your locations. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameSchedule;
/** The subsequent subscriptions after the start of a paid subscription for a product or service you offer. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameSubscriptionHeartbeat __attribute((deprecated("This attribute is no longer used.")));
/** The start of a free trial of a product or service you offer (example: trial subscription). */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameStartTrial;
/** The submission of an application for a product, service or program you offer (example: credit card, educational program or job). */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameSubmitApplication;
/** The start of a paid subscription for a product or service you offer. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameSubscribe;
/** Log this event when the user views an ad. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameAdImpression;
/** Log this event when the user clicks an ad. */
FOUNDATION_EXPORT FBSDKAppEventName FBSDKAppEventNameAdClick;
/**
@methodgroup Predefined event name parameters for common additional information to accompany events logged through the `logEvent` family
of methods on `FBSDKAppEvents`. Common event names are provided in the `FBAppEventName*` constants.
*/
/// typedef for FBSDKAppEventParameterName
typedef NSString *const FBSDKAppEventParameterName NS_TYPED_EXTENSIBLE_ENUM NS_SWIFT_NAME(AppEvents.ParameterName);
/**
* Parameter key used to specify data for the one or more pieces of content being logged about.
* Data should be a JSON encoded string.
* Example:
* "[{\"id\": \"1234\", \"quantity\": 2, \"item_price\": 5.99}, {\"id\": \"5678\", \"quantity\": 1, \"item_price\": 9.99}]"
*/
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameContent;
/** Parameter key used to specify an ID for the specific piece of content being logged about. Could be an EAN, article identifier, etc., depending on the nature of the app. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameContentID;
/** Parameter key used to specify a generic content type/family for the logged event, e.g. "music", "photo", "video". Options to use will vary based upon what the app is all about. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameContentType;
/** Parameter key used to specify currency used with logged event. E.g. "USD", "EUR", "GBP". See ISO-4217 for specific values. One reference for these is <http://en.wikipedia.org/wiki/ISO_4217>. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameCurrency;
/** Parameter key used to specify a description appropriate to the event being logged. E.g., the name of the achievement unlocked in the `FBAppEventNameAchievementUnlocked` event. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameDescription;
/** Parameter key used to specify the level achieved in a `FBAppEventNameAchieved` event. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameLevel;
/** Parameter key used to specify the maximum rating available for the `FBAppEventNameRate` event. E.g., "5" or "10". */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameMaxRatingValue;
/** Parameter key used to specify how many items are being processed for an `FBAppEventNameInitiatedCheckout` or `FBAppEventNamePurchased` event. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameNumItems;
/** Parameter key used to specify whether payment info is available for the `FBAppEventNameInitiatedCheckout` event. `FBSDKAppEventParameterValueYes` and `FBSDKAppEventParameterValueNo` are good canonical values to use for this parameter. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNamePaymentInfoAvailable;
/** Parameter key used to specify method user has used to register for the app, e.g., "Facebook", "email", "Twitter", etc */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameRegistrationMethod;
/** Parameter key used to specify the string provided by the user for a search operation. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameSearchString;
/** Parameter key used to specify whether the activity being logged about was successful or not. `FBSDKAppEventParameterValueYes` and `FBSDKAppEventParameterValueNo` are good canonical values to use for this parameter. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameSuccess;
/**
@methodgroup Predefined event name parameters for common additional information to accompany events logged through the `logProductItem` method on `FBSDKAppEvents`.
*/
/// typedef for FBSDKAppEventParameterProduct
typedef NSString *const FBSDKAppEventParameterProduct NS_TYPED_EXTENSIBLE_ENUM NS_SWIFT_NAME(AppEvents.ParameterProduct);
/** Parameter key used to specify the product item's category. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductCategory;
/** Parameter key used to specify the product item's custom label 0. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductCustomLabel0;
/** Parameter key used to specify the product item's custom label 1. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductCustomLabel1;
/** Parameter key used to specify the product item's custom label 2. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductCustomLabel2;
/** Parameter key used to specify the product item's custom label 3. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductCustomLabel3;
/** Parameter key used to specify the product item's custom label 4. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductCustomLabel4;
/** Parameter key used to specify the product item's AppLink app URL for iOS. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIOSUrl;
/** Parameter key used to specify the product item's AppLink app ID for iOS App Store. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIOSAppStoreID;
/** Parameter key used to specify the product item's AppLink app name for iOS. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIOSAppName;
/** Parameter key used to specify the product item's AppLink app URL for iPhone. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIPhoneUrl;
/** Parameter key used to specify the product item's AppLink app ID for iPhone App Store. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIPhoneAppStoreID;
/** Parameter key used to specify the product item's AppLink app name for iPhone. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIPhoneAppName;
/** Parameter key used to specify the product item's AppLink app URL for iPad. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIPadUrl;
/** Parameter key used to specify the product item's AppLink app ID for iPad App Store. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIPadAppStoreID;
/** Parameter key used to specify the product item's AppLink app name for iPad. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkIPadAppName;
/** Parameter key used to specify the product item's AppLink app URL for Android. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkAndroidUrl;
/** Parameter key used to specify the product item's AppLink fully-qualified package name for intent generation. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkAndroidPackage;
/** Parameter key used to specify the product item's AppLink app name for Android. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkAndroidAppName;
/** Parameter key used to specify the product item's AppLink app URL for Windows Phone. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkWindowsPhoneUrl;
/** Parameter key used to specify the product item's AppLink app ID, as a GUID, for App Store. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkWindowsPhoneAppID;
/** Parameter key used to specify the product item's AppLink app name for Windows Phone. */
FOUNDATION_EXPORT FBSDKAppEventParameterProduct FBSDKAppEventParameterProductAppLinkWindowsPhoneAppName;
/*
@methodgroup Predefined values to assign to event parameters that accompany events logged through the `logEvent` family
of methods on `FBSDKAppEvents`. Common event parameters are provided in the `FBSDKAppEventParameterName*` constants.
*/
/// typedef for FBSDKAppEventParameterValue
typedef NSString *const FBSDKAppEventParameterValue NS_TYPED_EXTENSIBLE_ENUM NS_SWIFT_NAME(AppEvents.ParameterValue);
/** Yes-valued parameter value to be used with parameter keys that need a Yes/No value */
FOUNDATION_EXPORT FBSDKAppEventParameterValue FBSDKAppEventParameterValueYes;
/** No-valued parameter value to be used with parameter keys that need a Yes/No value */
FOUNDATION_EXPORT FBSDKAppEventParameterValue FBSDKAppEventParameterValueNo;
/** Parameter key used to specify the type of ad in an FBSDKAppEventNameAdImpression
* or FBSDKAppEventNameAdClick event.
* E.g. "banner", "interstitial", "rewarded_video", "native" */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameAdType;
/** Parameter key used to specify the unique ID for all events within a subscription
* in an FBSDKAppEventNameSubscribe or FBSDKAppEventNameStartTrial event. */
FOUNDATION_EXPORT FBSDKAppEventParameterName FBSDKAppEventParameterNameOrderID;
/*
@methodgroup Predefined values to assign to user data store
*/
/// typedef for FBSDKAppEventUserDataType
typedef NSString *const FBSDKAppEventUserDataType NS_TYPED_EXTENSIBLE_ENUM NS_SWIFT_NAME(AppEvents.UserDataType);
/** Parameter key used to specify user's email. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventEmail;
/** Parameter key used to specify user's first name. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventFirstName;
/** Parameter key used to specify user's last name. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventLastName;
/** Parameter key used to specify user's phone. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventPhone;
/** Parameter key used to specify user's date of birth. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventDateOfBirth;
/** Parameter key used to specify user's gender. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventGender;
/** Parameter key used to specify user's city. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventCity;
/** Parameter key used to specify user's state. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventState;
/** Parameter key used to specify user's zip. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventZip;
/** Parameter key used to specify user's country. */
FOUNDATION_EXPORT FBSDKAppEventUserDataType FBSDKAppEventCountry;
/**
Client-side event logging for specialized application analytics available through Facebook App Insights
and for use with Facebook Ads conversion tracking and optimization.
The `FBSDKAppEvents` static class has a few related roles:
+ Logging predefined and application-defined events to Facebook App Insights with a
numeric value to sum across a large number of events, and an optional set of key/value
parameters that define "segments" for this event (e.g., 'purchaserStatus' : 'frequent', or
'gamerLevel' : 'intermediate')
+ Logging events to later be used for ads optimization around lifetime value.
+ Methods that control the way in which events are flushed out to the Facebook servers.
Here are some important characteristics of the logging mechanism provided by `FBSDKAppEvents`:
+ Events are not sent immediately when logged. They're cached and flushed out to the Facebook servers
in a number of situations:
- when an event count threshold is passed (currently 100 logged events).
- when a time threshold is passed (currently 15 seconds).
- when an app has gone to background and is then brought back to the foreground.
+ Events will be accumulated when the app is in a disconnected state, and sent when the connection is
restored and one of the above 'flush' conditions are met.
+ The `FBSDKAppEvents` class is thread-safe in that events may be logged from any of the app's threads.
+ The developer can set the `flushBehavior` on `FBSDKAppEvents` to force the flushing of events to only
occur on an explicit call to the `flush` method.
+ The developer can turn on console debug output for event logging and flushing to the server by using
the `FBSDKLoggingBehaviorAppEvents` value in `[FBSettings setLoggingBehavior:]`.
Some things to note when logging events:
+ There is a limit on the number of unique event names an app can use, on the order of 1000.
+ There is a limit to the number of unique parameter names in the provided parameters that can
be used per event, on the order of 25. This is not just for an individual call, but for all
invocations for that eventName.
+ Event names and parameter names (the keys in the NSDictionary) must be between 2 and 40 characters, and
must consist of alphanumeric characters, _, -, or spaces.
+ The length of each parameter value can be no more than on the order of 100 characters.
*/
NS_SWIFT_NAME(AppEvents)
@interface FBSDKAppEvents : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/*
* Control over event batching/flushing
*/
/**
The current event flushing behavior specifying when events are sent back to Facebook servers.
*/
@property (class, nonatomic, assign) FBSDKAppEventsFlushBehavior flushBehavior;
/**
Set the 'override' App ID for App Event logging.
In some cases, apps want to use one Facebook App ID for login and social presence and another
for App Event logging. (An example is if multiple apps from the same company share an app ID for login, but
want distinct logging.) By default, this value is `nil`, and defers to the `FBSDKAppEventsOverrideAppIDBundleKey`
plist value. If that's not set, it defaults to `[FBSDKSettings appID]`.
This should be set before any other calls are made to `FBSDKAppEvents`. Thus, you should set it in your application
delegate's `application:didFinishLaunchingWithOptions:` delegate.
*/
@property (class, nonatomic, copy, nullable) NSString *loggingOverrideAppID;
/*
The custom user ID to associate with all app events.
The userID is persisted until it is cleared by passing nil.
*/
@property (class, nonatomic, copy, nullable) NSString *userID;
/*
* Basic event logging
*/
/**
Log an event with just an eventName.
@param eventName The name of the event to record. Limitations on number of events and name length
are given in the `FBSDKAppEvents` documentation.
*/
+ (void)logEvent:(FBSDKAppEventName)eventName;
/**
Log an event with an eventName and a numeric value to be aggregated with other events of this name.
@param eventName The name of the event to record. Limitations on number of events and name length
are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants.
@param valueToSum Amount to be aggregated into all events of this eventName, and App Insights will report
the cumulative and average value of this amount.
*/
+ (void)logEvent:(FBSDKAppEventName)eventName
valueToSum:(double)valueToSum;
/**
Log an event with an eventName and a set of key/value pairs in the parameters dictionary.
Parameter limitations are described above.
@param eventName The name of the event to record. Limitations on number of events and name construction
are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants.
@param parameters Arbitrary parameter dictionary of characteristics. The keys to this dictionary must
be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of
parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names
are provided in `FBSDKAppEventParameterName*` constants.
*/
+ (void)logEvent:(FBSDKAppEventName)eventName
parameters:(NSDictionary<FBSDKAppEventParameterName, id> *)parameters;
/**
Log an event with an eventName, a numeric value to be aggregated with other events of this name,
and a set of key/value pairs in the parameters dictionary.
@param eventName The name of the event to record. Limitations on number of events and name construction
are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants.
@param valueToSum Amount to be aggregated into all events of this eventName, and App Insights will report
the cumulative and average value of this amount.
@param parameters Arbitrary parameter dictionary of characteristics. The keys to this dictionary must
be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of
parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names
are provided in `FBSDKAppEventParameterName*` constants.
*/
+ (void)logEvent:(FBSDKAppEventName)eventName
valueToSum:(double)valueToSum
parameters:(NSDictionary<FBSDKAppEventParameterName, id> *)parameters;
/**
Log an event with an eventName, a numeric value to be aggregated with other events of this name,
and a set of key/value pairs in the parameters dictionary. Providing session lets the developer
target a particular <FBSession>. If nil is provided, then `[FBSession activeSession]` will be used.
@param eventName The name of the event to record. Limitations on number of events and name construction
are given in the `FBSDKAppEvents` documentation. Common event names are provided in `FBAppEventName*` constants.
@param valueToSum Amount to be aggregated into all events of this eventName, and App Insights will report
the cumulative and average value of this amount. Note that this is an NSNumber, and a value of `nil` denotes
that this event doesn't have a value associated with it for summation.
@param parameters Arbitrary parameter dictionary of characteristics. The keys to this dictionary must
be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of
parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names
are provided in `FBSDKAppEventParameterName*` constants.
@param accessToken The optional access token to log the event as.
*/
+ (void)logEvent:(FBSDKAppEventName)eventName
valueToSum:(nullable NSNumber *)valueToSum
parameters:(NSDictionary<FBSDKAppEventParameterName, id> *)parameters
accessToken:(nullable FBSDKAccessToken *)accessToken;
/*
* Purchase logging
*/
/**
Log a purchase of the specified amount, in the specified currency.
@param purchaseAmount Purchase amount to be logged, as expressed in the specified currency. This value
will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346).
@param currency Currency, is denoted as, e.g. "USD", "EUR", "GBP". See ISO-4217 for
specific values. One reference for these is <http://en.wikipedia.org/wiki/ISO_4217>.
This event immediately triggers a flush of the `FBSDKAppEvents` event queue, unless the `flushBehavior` is set
to `FBSDKAppEventsFlushBehaviorExplicitOnly`.
*/
+ (void)logPurchase:(double)purchaseAmount
currency:(NSString *)currency;
/**
Log a purchase of the specified amount, in the specified currency, also providing a set of
additional characteristics describing the purchase.
@param purchaseAmount Purchase amount to be logged, as expressed in the specified currency.This value
will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346).
@param currency Currency, is denoted as, e.g. "USD", "EUR", "GBP". See ISO-4217 for
specific values. One reference for these is <http://en.wikipedia.org/wiki/ISO_4217>.
@param parameters Arbitrary parameter dictionary of characteristics. The keys to this dictionary must
be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of
parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names
are provided in `FBSDKAppEventParameterName*` constants.
This event immediately triggers a flush of the `FBSDKAppEvents` event queue, unless the `flushBehavior` is set
to `FBSDKAppEventsFlushBehaviorExplicitOnly`.
*/
+ (void)logPurchase:(double)purchaseAmount
currency:(NSString *)currency
parameters:(NSDictionary<NSString *, id> *)parameters;
/**
Log a purchase of the specified amount, in the specified currency, also providing a set of
additional characteristics describing the purchase, as well as an <FBSession> to log to.
@param purchaseAmount Purchase amount to be logged, as expressed in the specified currency.This value
will be rounded to the thousandths place (e.g., 12.34567 becomes 12.346).
@param currency Currency, is denoted as, e.g. "USD", "EUR", "GBP". See ISO-4217 for
specific values. One reference for these is <http://en.wikipedia.org/wiki/ISO_4217>.
@param parameters Arbitrary parameter dictionary of characteristics. The keys to this dictionary must
be NSString's, and the values are expected to be NSString or NSNumber. Limitations on the number of
parameters and name construction are given in the `FBSDKAppEvents` documentation. Commonly used parameter names
are provided in `FBSDKAppEventParameterName*` constants.
@param accessToken The optional access token to log the event as.
This event immediately triggers a flush of the `FBSDKAppEvents` event queue, unless the `flushBehavior` is set
to `FBSDKAppEventsFlushBehaviorExplicitOnly`.
*/
+ (void)logPurchase:(double)purchaseAmount
currency:(NSString *)currency
parameters:(NSDictionary<NSString *, id> *)parameters
accessToken:(nullable FBSDKAccessToken *)accessToken;
/*
* Push Notifications Logging
*/
/**
Log an app event that tracks that the application was open via Push Notification.
@param payload Notification payload received via `UIApplicationDelegate`.
*/
+ (void)logPushNotificationOpen:(NSDictionary *)payload;
/**
Log an app event that tracks that a custom action was taken from a push notification.
@param payload Notification payload received via `UIApplicationDelegate`.
@param action Name of the action that was taken.
*/
+ (void)logPushNotificationOpen:(NSDictionary *)payload action:(NSString *)action;
/**
Uploads product catalog product item as an app event
@param itemID Unique ID for the item. Can be a variant for a product.
Max size is 100.
@param availability If item is in stock. Accepted values are:
in stock - Item ships immediately
out of stock - No plan to restock
preorder - Available in future
available for order - Ships in 1-2 weeks
discontinued - Discontinued
@param condition Product condition: new, refurbished or used.
@param description Short text describing product. Max size is 5000.
@param imageLink Link to item image used in ad.
@param link Link to merchant's site where someone can buy the item.
@param title Title of item.
@param priceAmount Amount of purchase, in the currency specified by the 'currency'
parameter. This value will be rounded to the thousandths place
(e.g., 12.34567 becomes 12.346).
@param currency Currency used to specify the amount.
E.g. "USD", "EUR", "GBP". See ISO-4217 for specific values. One reference for these is <http://en.wikipedia.org/wiki/ISO_4217>
@param gtin Global Trade Item Number including UPC, EAN, JAN and ISBN
@param mpn Unique manufacture ID for product
@param brand Name of the brand
Note: Either gtin, mpn or brand is required.
@param parameters Optional fields for deep link specification.
*/
+ (void)logProductItem:(NSString *)itemID
availability:(FBSDKProductAvailability)availability
condition:(FBSDKProductCondition)condition
description:(NSString *)description
imageLink:(NSString *)imageLink
link:(NSString *)link
title:(NSString *)title
priceAmount:(double)priceAmount
currency:(NSString *)currency
gtin:(nullable NSString *)gtin
mpn:(nullable NSString *)mpn
brand:(nullable NSString *)brand
parameters:(nullable NSDictionary<NSString *, id> *)parameters;
/**
Notifies the events system that the app has launched and, when appropriate, logs an "activated app" event.
This function is called automatically from FBSDKApplicationDelegate applicationDidBecomeActive, unless
one overrides 'FacebookAutoLogAppEventsEnabled' key to false in the project info plist file.
In case 'FacebookAutoLogAppEventsEnabled' is set to false, then it should typically be placed in the
app delegates' `applicationDidBecomeActive:` method.
This method also takes care of logging the event indicating the first time this app has been launched, which, among other things, is used to
track user acquisition and app install ads conversions.
`activateApp` will not log an event on every app launch, since launches happen every time the app is backgrounded and then foregrounded.
"activated app" events will be logged when the app has not been active for more than 60 seconds. This method also causes a "deactivated app"
event to be logged when sessions are "completed", and these events are logged with the session length, with an indication of how much
time has elapsed between sessions, and with the number of background/foreground interruptions that session had. This data
is all visible in your app's App Events Insights.
*/
+ (void)activateApp;
/*
* Push Notifications Registration and Uninstall Tracking
*/
/**
Sets and sends device token to register the current application for push notifications.
Sets and sends a device token from `NSData` representation that you get from `UIApplicationDelegate.-application:didRegisterForRemoteNotificationsWithDeviceToken:`.
@param deviceToken Device token data.
*/
+ (void)setPushNotificationsDeviceToken:(NSData *)deviceToken;
/**
Sets and sends device token string to register the current application for push notifications.
Sets and sends a device token string
@param deviceTokenString Device token string.
*/
+ (void)setPushNotificationsDeviceTokenString:(NSString *)deviceTokenString
NS_SWIFT_NAME(setPushNotificationsDeviceToken(_:));
/**
Explicitly kick off flushing of events to Facebook. This is an asynchronous method, but it does initiate an immediate
kick off. Server failures will be reported through the NotificationCenter with notification ID `FBSDKAppEventsLoggingResultNotification`.
*/
+ (void)flush;
/**
Creates a request representing the Graph API call to retrieve a Custom Audience "third party ID" for the app's Facebook user.
Callers will send this ID back to their own servers, collect up a set to create a Facebook Custom Audience with,
and then use the resultant Custom Audience to target ads.
The JSON in the request's response will include an "custom_audience_third_party_id" key/value pair, with the value being the ID retrieved.
This ID is an encrypted encoding of the Facebook user's ID and the invoking Facebook app ID.
Multiple calls with the same user will return different IDs, thus these IDs cannot be used to correlate behavior
across devices or applications, and are only meaningful when sent back to Facebook for creating Custom Audiences.
The ID retrieved represents the Facebook user identified in the following way: if the specified access token is valid,
the ID will represent the user associated with that token; otherwise the ID will represent the user logged into the
native Facebook app on the device. If there is no native Facebook app, no one is logged into it, or the user has opted out
at the iOS level from ad tracking, then a `nil` ID will be returned.
This method returns `nil` if either the user has opted-out (via iOS) from Ad Tracking, the app itself has limited event usage
via the `[FBSDKSettings limitEventAndDataUsage]` flag, or a specific Facebook user cannot be identified.
@param accessToken The access token to use to establish the user's identity for users logged into Facebook through this app.
If `nil`, then the `[FBSDKAccessToken currentAccessToken]` is used.
*/
+ (nullable FBSDKGraphRequest *)requestForCustomAudienceThirdPartyIDWithAccessToken:(nullable FBSDKAccessToken *)accessToken;
/*
Clears the custom user ID to associate with all app events.
*/
+ (void)clearUserID;
/*
Sets custom user data to associate with all app events. All user data are hashed
and used to match Facebook user from this instance of an application.
The user data will be persisted between application instances.
@param email user's email
@param firstName user's first name
@param lastName user's last name
@param phone user's phone
@param dateOfBirth user's date of birth
@param gender user's gender
@param city user's city
@param state user's state
@param zip user's zip
@param country user's country
*/
+ (void)setUserEmail:(nullable NSString *)email
firstName:(nullable NSString *)firstName
lastName:(nullable NSString *)lastName
phone:(nullable NSString *)phone
dateOfBirth:(nullable NSString *)dateOfBirth
gender:(nullable NSString *)gender
city:(nullable NSString *)city
state:(nullable NSString *)state
zip:(nullable NSString *)zip
country:(nullable NSString *)country
NS_SWIFT_NAME(setUser(email:firstName:lastName:phone:dateOfBirth:gender:city:state:zip:country:));
/*
Returns the set user data else nil
*/
+ (nullable NSString *)getUserData;
/*
Clears the current user data
*/
+ (void)clearUserData;
/*
Sets custom user data to associate with all app events. All user data are hashed
and used to match Facebook user from this instance of an application.
The user data will be persisted between application instances.
@param data data
@param type data type, e.g. FBSDKAppEventEmail, FBSDKAppEventPhone
*/
+ (void)setUserData:(nullable NSString *)data
forType:(FBSDKAppEventUserDataType)type;
/*
Clears the current user data of certain type
*/
+ (void)clearUserDataForType:(FBSDKAppEventUserDataType)type;
/*
Sends a request to update the properties for the current user, set by `setUserID:`
You must call `FBSDKAppEvents setUserID:` before making this call.
@param properties the custom user properties
@param handler the optional completion handler
*/
+ (void)updateUserProperties:(NSDictionary<NSString *, id> *)properties handler:(nullable FBSDKGraphRequestBlock)handler;
#if !TARGET_OS_TV
/*
Intended to be used as part of a hybrid webapp.
If you call this method, the FB SDK will inject a new JavaScript object into your webview.
If the FB Pixel is used within the webview, and references the app ID of this app,
then it will detect the presence of this injected JavaScript object
and pass Pixel events back to the FB SDK for logging using the AppEvents framework.
@param webView The webview to augment with the additional JavaScript behaviour
*/
+ (void)augmentHybridWKWebView:(WKWebView *)webView;
#endif
/*
* Unity helper functions
*/
/**
Set if the Unity is already initialized
@param isUnityInit whether Unity is initialized.
*/
+ (void)setIsUnityInit:(BOOL)isUnityInit;
/*
Send event binding to Unity
*/
+ (void)sendEventBindingsToUnity;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKMetadataIndexer : NSObject
+ (void)enable;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,329 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKMetadataIndexer.h"
#import <objc/runtime.h>
#import <sys/sysctl.h>
#import <sys/utsname.h>
#import <UIKit/UIKit.h>
#import "FBSDKCoreKit+Internal.h"
static const int FBSDKMetadataIndexerMaxTextLength = 100;
static const int FBSDKMetadataIndexerMaxIndicatorLength = 100;
static const int FBSDKMetadataIndexerMaxValue = 5;
static NSString * const FIELD_K = @"k";
static NSString * const FIELD_V = @"v";
static NSString * const FIELD_K_DELIMITER = @",";
FBSDKAppEventUserDataType FBSDKAppEventRule1 = @"r1";
FBSDKAppEventUserDataType FBSDKAppEventRule2 = @"r2";
static NSArray<FBSDKAppEventUserDataType> *FBSDKMetadataIndexerKeys;
static NSMutableDictionary<NSString *, NSDictionary<NSString *, NSString *> *> *_rules;
static NSMutableDictionary<NSString *, NSMutableArray<NSString *> *> *_store;
static dispatch_queue_t serialQueue;
@implementation FBSDKMetadataIndexer
+ (void)initialize
{
FBSDKMetadataIndexerKeys = @[FBSDKAppEventRule1, FBSDKAppEventRule2];
serialQueue = dispatch_queue_create("com.facebook.appevents.MetadataIndexer", DISPATCH_QUEUE_SERIAL);
}
+ (void)enable
{
if (FBSDKAdvertisingTrackingAllowed != [FBSDKAppEventsUtility advertisingTrackingStatus]) {
return;
}
[FBSDKServerConfigurationManager loadServerConfigurationWithCompletionBlock:^(FBSDKServerConfiguration *serverConfiguration, NSError *error) {
if (error) {
return;
}
[FBSDKMetadataIndexer setupWithRules:serverConfiguration.AAMRules];
}];
}
+ (void)setupWithRules:(NSDictionary<NSString *, id> * _Nullable)rules
{
if (0 == rules.count) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[FBSDKMetadataIndexer constructRules:rules];
[FBSDKMetadataIndexer initStore];
BOOL isEnabled = NO;
for (NSString *key in FBSDKMetadataIndexerKeys) {
BOOL isRuleEnabled = (nil != [_rules objectForKey:key]);
if (isRuleEnabled) {
isEnabled = YES;
}
if (!isRuleEnabled) {
[_store removeObjectForKey:key];
[FBSDKUserDataStore setHashData:nil forType:key];
}
}
if (isEnabled) {
[FBSDKMetadataIndexer setupMetadataIndexing];
}
});
}
+ (void)initStore
{
_store = [[NSMutableDictionary alloc] init];
for (NSString *key in FBSDKMetadataIndexerKeys) {
NSString *data = [FBSDKUserDataStore getHashedDataForType:key];
if (data.length > 0) {
_store[key] = [NSMutableArray arrayWithArray:[data componentsSeparatedByString:FIELD_K_DELIMITER]];
}
}
for (NSString *key in FBSDKMetadataIndexerKeys) {
if (!_store[key]) {
_store[key] = [[NSMutableArray alloc] init];
}
}
}
+ (void)constructRules:(NSDictionary<NSString *, id> * _Nullable)rules
{
if (!_rules) {
_rules = [[NSMutableDictionary alloc] init];
}
for (NSString *key in rules) {
NSDictionary<NSString *, NSString *> *value = [FBSDKTypeUtility dictionaryValue:rules[key]];
if (value && value[FIELD_K].length > 0 && value[FIELD_V].length > 0) {
_rules[key] = value;
}
}
}
+ (void)setupMetadataIndexing
{
void (^block)(UIView *) = ^(UIView *view) {
// Indexing when the view is removed from window and conforms to UITextInput, and skip UIFieldEditor, which is an internval view of UITextField
if (![view window] && ![NSStringFromClass([view class]) isEqualToString:@"UIFieldEditor"] && [view conformsToProtocol:@protocol(UITextInput)]) {
NSString *text = [FBSDKViewHierarchy getText:view];
NSString *placeholder = [FBSDKViewHierarchy getHint:view];
BOOL secureTextEntry = [self checkSecureTextEntry:view];
NSArray<NSString *> *labels = [self getLabelsOfView:view];
UIKeyboardType keyboardType = [self getKeyboardType:view];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[self getMetadataWithText:[self normalizedValue:text]
placeholder:[self normalizeField:placeholder]
labels:labels
secureTextEntry:secureTextEntry
inputType:keyboardType];
});
}
};
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UIView class] withBlock:block named:@"metadataIndexingUIView"];
// iOS 12: UITextField implements didMoveToWindow without calling parent implementation
if (@available(iOS 12, *)) {
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UITextField class] withBlock:block named:@"metadataIndexingUITextField"];
} else {
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UIControl class] withBlock:block named:@"metadataIndexingUIControl"];
}
}
+ (NSArray<UIView *> *)getSiblingViewsOfView:(UIView *)view
{
NSObject *parent = [FBSDKViewHierarchy getParent:view];
if (parent) {
NSArray<id> *views = [FBSDKViewHierarchy getChildren:parent];
if (views) {
NSMutableArray<id> *siblings = [NSMutableArray arrayWithArray:views];
[siblings removeObject:view];
return [siblings copy];
}
}
return nil;
}
+ (NSArray<NSString *> *)getLabelsOfView:(UIView *)view
{
NSMutableArray<NSString *> *labels = [[NSMutableArray alloc] init];
NSString *placeholder = [self normalizeField:[FBSDKViewHierarchy getHint:view]];
if (placeholder) {
[labels addObject:placeholder];
}
NSArray<id> *siblingViews = [self getSiblingViewsOfView:view];
for (id sibling in siblingViews) {
if ([sibling isKindOfClass:[UILabel class]]) {
NSString *text = [self normalizeField:[FBSDKViewHierarchy getText:sibling]];
if (text) {
[labels addObject:text];
}
}
}
return [labels copy];
}
+ (BOOL)checkSecureTextEntry:(UIView *)view
{
if ([view isKindOfClass:[UITextField class]]) {
return ((UITextField *)view).secureTextEntry;
}
if ([view isKindOfClass:[UITextView class]]) {
return ((UITextView *)view).secureTextEntry;
}
return NO;
}
+ (UIKeyboardType)getKeyboardType:(UIView *)view
{
if ([view isKindOfClass:[UITextField class]]) {
return ((UITextField *)view).keyboardType;
}
if ([view isKindOfClass:[UITextView class]]) {
return ((UITextView *)view).keyboardType;
}
return UIKeyboardTypeDefault;
}
+ (void)getMetadataWithText:(NSString *)text
placeholder:(NSString *)placeholder
labels:(NSArray<NSString *> *)labels
secureTextEntry:(BOOL)secureTextEntry
inputType:(UIKeyboardType)inputType
{
if (secureTextEntry ||
[placeholder containsString:@"password"] ||
text.length == 0 ||
text.length > FBSDKMetadataIndexerMaxTextLength ||
placeholder.length >= FBSDKMetadataIndexerMaxIndicatorLength) {
return;
}
for (NSString *key in _rules) {
NSDictionary<NSString *, NSString *> *rule = _rules[key];
BOOL isRuleKMatched = [self checkMetadataHint:placeholder matchRuleK:rule[FIELD_K]]
|| [self checkMetadataLabels:labels matchRuleK:rule[FIELD_K]];
BOOL isRuleVMatched = [self checkMetadataText:text matchRuleV:rule[FIELD_V]];
if (isRuleKMatched && isRuleVMatched) {
[FBSDKMetadataIndexer checkAndAppendData:text forKey:key];
}
}
}
#pragma mark - Helper Methods
+ (void)checkAndAppendData:(NSString *)data
forKey:(NSString *)key
{
NSString *hashData = [FBSDKUtility SHA256Hash:data];
dispatch_async(serialQueue, ^{
if (hashData.length == 0 || [_store[key] containsObject:hashData]) {
return;
}
while (_store[key].count >= FBSDKMetadataIndexerMaxValue) {
[_store[key] removeObjectAtIndex:0];
}
[_store[key] addObject:hashData];
[FBSDKUserDataStore setHashData:[_store[key] componentsJoinedByString:@","]
forType:key];
});
}
+ (BOOL)checkMetadataLabels:(NSArray<NSString *> *)labels
matchRuleK:(NSString *)ruleK
{
for (NSString *label in labels) {
if ([self checkMetadataHint:label matchRuleK:ruleK]) {
return YES;
}
}
return NO;
}
+ (BOOL)checkMetadataHint:(NSString *)hint
matchRuleK:(NSString *)ruleK
{
if (hint.length > 0 && ruleK) {
NSArray<NSString *> *items = [ruleK componentsSeparatedByString:@","];
for (NSString *item in items) {
if ([hint containsString:item]) {
return YES;
}
}
}
return NO;
}
+ (BOOL)checkMetadataText:(NSString *)text
matchRuleV:(NSString *)ruleV
{
if (text.length > 0 && ruleV) {
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:ruleV
options:NSRegularExpressionCaseInsensitive
error:nil];
NSUInteger matches = [regex numberOfMatchesInString:text options:0 range:NSMakeRange(0, text.length)];
NSString *prunedText = [[text componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"+- ()."]] componentsJoinedByString:@""];
NSUInteger prunedMatches = [regex numberOfMatchesInString:prunedText options:0 range:NSMakeRange(0, prunedText.length)];
return matches > 0 || prunedMatches > 0;
}
return NO;
}
+ (NSString *)normalizeField:(NSString *)field
{
if (!field) {
return nil;
}
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[_-]|\\s"
options:NSRegularExpressionCaseInsensitive
error:nil];
return [regex stringByReplacingMatchesInString:field
options:0
range:NSMakeRange(0, field.length)
withTemplate:@""].lowercaseString;
}
+ (NSString *)normalizedValue:(NSString *)value
{
if (!value) {
return nil;
}
return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].lowercaseString;
}
@end
#endif

View File

@ -0,0 +1,40 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^FBSDKCodelessSettingLoadBlock)(BOOL isCodelessSetupEnabled, NSError *_Nullable error);
NS_SWIFT_NAME(CodelessIndexer)
@interface FBSDKCodelessIndexer : NSObject
@property (class, nonatomic, copy, readonly) NSString *extInfo;
+ (void)enable;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,414 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKCodelessIndexer.h"
#import <objc/runtime.h>
#import <sys/sysctl.h>
#import <sys/utsname.h>
#import <UIKit/UIKit.h>
#import "FBSDKCoreKit+Internal.h"
#import "FBSDKGraphRequest.h"
#import "FBSDKSettings.h"
@implementation FBSDKCodelessIndexer
static BOOL _isCodelessIndexing;
static BOOL _isCheckingSession;
static BOOL _isCodelessIndexingEnabled;
static BOOL _isGestureSet;
static NSMutableDictionary<NSString *, id> *_codelessSetting;
static const NSTimeInterval kTimeout = 4.0;
static NSString *_deviceSessionID;
static NSTimer *_appIndexingTimer;
static NSString *_lastTreeHash;
+ (void)enable
{
if (_isGestureSet) {
return;
}
#if TARGET_OS_SIMULATOR
[self setupGesture];
#else
[self loadCodelessSettingWithCompletionBlock:^(BOOL isCodelessSetupEnabled, NSError *error) {
if (isCodelessSetupEnabled) {
[self setupGesture];
}
}];
#endif
}
// DO NOT call this function, it is only called once in the load function
+ (void)loadCodelessSettingWithCompletionBlock:(FBSDKCodelessSettingLoadBlock)completionBlock
{
NSString *appID = [FBSDKSettings appID];
if (appID == nil) {
return;
}
[FBSDKServerConfigurationManager loadServerConfigurationWithCompletionBlock:^(FBSDKServerConfiguration *serverConfiguration, NSError *serverConfigurationLoadingError) {
if (!serverConfiguration.codelessEventsEnabled) {
return;
}
// load the defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *defaultKey = [NSString stringWithFormat:CODELESS_SETTING_KEY, appID];
NSData *data = [defaults objectForKey:defaultKey];
if ([data isKindOfClass:[NSData class]]) {
NSMutableDictionary<NSString *, id> *codelessSetting = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (codelessSetting) {
_codelessSetting = codelessSetting;
}
}
if (!_codelessSetting) {
_codelessSetting = [[NSMutableDictionary alloc] init];
}
if (![self _codelessSetupTimestampIsValid:[_codelessSetting objectForKey:CODELESS_SETTING_TIMESTAMP_KEY]]) {
FBSDKGraphRequest *request = [self requestToLoadCodelessSetup:appID];
if (request == nil) {
return;
}
FBSDKGraphRequestConnection *requestConnection = [[FBSDKGraphRequestConnection alloc] init];
requestConnection.timeout = kTimeout;
[requestConnection addRequest:request completionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *codelessLoadingError) {
if (codelessLoadingError) {
return;
}
NSDictionary<NSString *, id> *resultDictionary = [FBSDKTypeUtility dictionaryValue:result];
if (resultDictionary) {
BOOL isCodelessSetupEnabled = [FBSDKTypeUtility boolValue:resultDictionary[CODELESS_SETUP_ENABLED_FIELD]];
[_codelessSetting setObject:@(isCodelessSetupEnabled) forKey:CODELESS_SETUP_ENABLED_KEY];
[_codelessSetting setObject:[NSDate date] forKey:CODELESS_SETTING_TIMESTAMP_KEY];
// update the cached copy in user defaults
[defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:_codelessSetting] forKey:defaultKey];
completionBlock(isCodelessSetupEnabled, codelessLoadingError);
}
}];
[requestConnection start];
} else {
completionBlock([FBSDKTypeUtility boolValue:[_codelessSetting objectForKey:CODELESS_SETUP_ENABLED_KEY]], nil);
}
}];
}
+ (FBSDKGraphRequest *)requestToLoadCodelessSetup:(NSString *)appID
{
NSString *advertiserID = [FBSDKAppEventsUtility advertiserID];
if (!advertiserID) {
return nil;
}
NSDictionary<NSString *, NSString *> *parameters = @{
@"fields": CODELESS_SETUP_ENABLED_FIELD,
@"advertiser_id": advertiserID
};
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:appID
parameters:parameters
tokenString:nil
HTTPMethod:nil
flags:FBSDKGraphRequestFlagSkipClientToken | FBSDKGraphRequestFlagDisableErrorRecovery];
return request;
}
+ (BOOL)_codelessSetupTimestampIsValid:(NSDate *)timestamp
{
return (timestamp != nil && [[NSDate date] timeIntervalSinceDate:timestamp] < CODELESS_SETTING_CACHE_TIMEOUT);
}
+ (void)setupGesture
{
_isGestureSet = YES;
[UIApplication sharedApplication].applicationSupportsShakeToEdit = YES;
Class class = [UIApplication class];
[FBSDKSwizzler swizzleSelector:@selector(motionBegan:withEvent:) onClass:class withBlock:^{
if ([FBSDKServerConfigurationManager cachedServerConfiguration].isCodelessEventsEnabled) {
[self checkCodelessIndexingSession];
}
} named:@"motionBegan"];
}
+ (void)checkCodelessIndexingSession
{
if (_isCheckingSession) return;
_isCheckingSession = YES;
NSDictionary *parameters = @{
CODELESS_INDEXING_SESSION_ID_KEY: [self currentSessionDeviceID],
CODELESS_INDEXING_EXT_INFO_KEY: [self extInfo]
};
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:[NSString stringWithFormat:@"%@/%@",
[FBSDKSettings appID], CODELESS_INDEXING_SESSION_ENDPOINT]
parameters:parameters
HTTPMethod:FBSDKHTTPMethodPOST];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
_isCheckingSession = NO;
if ([result isKindOfClass:[NSDictionary class]]) {
_isCodelessIndexingEnabled = [((NSDictionary *)result)[CODELESS_INDEXING_STATUS_KEY] boolValue];
if (_isCodelessIndexingEnabled) {
_lastTreeHash = nil;
if (!_appIndexingTimer) {
_appIndexingTimer = [NSTimer timerWithTimeInterval:CODELESS_INDEXING_UPLOAD_INTERVAL_IN_SECONDS
target:self
selector:@selector(startIndexing)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_appIndexingTimer forMode:NSDefaultRunLoopMode];
}
} else {
_deviceSessionID = nil;
}
}
}];
}
+ (NSString *)currentSessionDeviceID
{
if (!_deviceSessionID) {
_deviceSessionID = [NSUUID UUID].UUIDString;
}
return _deviceSessionID;
}
+ (NSString *)extInfo
{
struct utsname systemInfo;
uname(&systemInfo);
NSString *machine = @(systemInfo.machine);
NSString *advertiserID = [FBSDKAppEventsUtility advertiserID] ?: @"";
machine = machine ?: @"";
NSString *debugStatus = [FBSDKAppEventsUtility isDebugBuild] ? @"1" : @"0";
#if TARGET_IPHONE_SIMULATOR
NSString *isSimulator = @"1";
#else
NSString *isSimulator = @"0";
#endif
NSLocale *locale = [NSLocale currentLocale];
NSString *languageCode = [locale objectForKey:NSLocaleLanguageCode];
NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
NSString *localeString = locale.localeIdentifier;
if (languageCode && countryCode) {
localeString = [NSString stringWithFormat:@"%@_%@", languageCode, countryCode];
}
NSString *extinfo = [FBSDKBasicUtility JSONStringForObject:@[machine,
advertiserID,
debugStatus,
isSimulator,
localeString]
error:NULL
invalidObjectHandler:NULL];
return extinfo ?: @"";
}
+ (void)startIndexing {
if (!_isCodelessIndexingEnabled) {
return;
}
if (UIApplicationStateActive != [UIApplication sharedApplication].applicationState) {
return;
}
// If userAgentSuffix begins with Unity, trigger unity code to upload view hierarchy
NSString *userAgentSuffix = [FBSDKSettings userAgentSuffix];
if (userAgentSuffix != nil && [userAgentSuffix hasPrefix:@"Unity"]) {
Class FBUnityUtility = objc_lookUpClass("FBUnityUtility");
SEL selector = NSSelectorFromString(@"triggerUploadViewHierarchy");
if (FBUnityUtility && selector && [FBUnityUtility respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[FBUnityUtility performSelector:selector];
#pragma clang diagnostic pop
}
} else {
[self uploadIndexing];
}
}
+ (void)uploadIndexing
{
if (_isCodelessIndexing) {
return;
}
NSString *tree = [FBSDKCodelessIndexer currentViewTree];
[self uploadIndexing:tree];
}
+ (void)uploadIndexing:(NSString *)tree
{
if (_isCodelessIndexing) {
return;
}
if (!tree) {
return;
}
NSString *currentTreeHash = [FBSDKUtility SHA256Hash:tree];
if (_lastTreeHash && [_lastTreeHash isEqualToString:currentTreeHash]) {
return;
}
_lastTreeHash = currentTreeHash;
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *version = [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:[NSString stringWithFormat:@"%@/%@",
[FBSDKSettings appID], CODELESS_INDEXING_ENDPOINT]
parameters:@{
CODELESS_INDEXING_TREE_KEY: tree,
CODELESS_INDEXING_APP_VERSION_KEY: version ?: @"",
CODELESS_INDEXING_PLATFORM_KEY: @"iOS",
CODELESS_INDEXING_SESSION_ID_KEY: [self currentSessionDeviceID]
}
HTTPMethod:FBSDKHTTPMethodPOST];
_isCodelessIndexing = YES;
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
_isCodelessIndexing = NO;
if ([result isKindOfClass:[NSDictionary class]]) {
_isCodelessIndexingEnabled = [result[CODELESS_INDEXING_STATUS_KEY] boolValue];
if (!_isCodelessIndexingEnabled) {
_deviceSessionID = nil;
}
}
}];
}
+ (NSString *)currentViewTree
{
NSMutableArray *trees = [NSMutableArray array];
NSArray *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows) {
NSDictionary *tree = [FBSDKCodelessIndexer recursiveCaptureTree:window];
if (tree) {
if (window.isKeyWindow) {
[trees insertObject:tree atIndex:0];
} else {
[trees addObject:tree];
}
}
}
if (0 == trees.count) {
return nil;
}
NSArray *viewTrees = [trees reverseObjectEnumerator].allObjects;
NSData *data = UIImageJPEGRepresentation([FBSDKCodelessIndexer screenshot], 0.5);
NSString *screenshot = [data base64EncodedStringWithOptions:0];
NSMutableDictionary *treeInfo = [NSMutableDictionary dictionary];
treeInfo[@"view"] = viewTrees;
treeInfo[@"screenshot"] = screenshot ?: @"";
NSString *tree = nil;
data = [NSJSONSerialization dataWithJSONObject:treeInfo options:0 error:nil];
if (data) {
tree = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return tree;
}
+ (NSDictionary<NSString *, id> *)recursiveCaptureTree:(NSObject *)obj
{
if (!obj) {
return nil;
}
NSMutableDictionary *result = [FBSDKViewHierarchy getDetailAttributesOf:obj];
NSArray *children = [FBSDKViewHierarchy getChildren:obj];
NSMutableArray *childrenTrees = [NSMutableArray array];
for (NSObject *child in children) {
NSDictionary *objTree = [self recursiveCaptureTree:child];
[childrenTrees addObject:objTree];
}
if (childrenTrees.count > 0) {
[result setValue:[childrenTrees copy] forKey:VIEW_HIERARCHY_CHILD_VIEWS_KEY];
}
return [result copy];
}
+ (UIImage *)screenshot {
UIWindow *window = [UIApplication sharedApplication].delegate.window;
UIGraphicsBeginImageContext(window.bounds.size);
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (NSDictionary<NSString *, NSNumber *> *)dimensionOf:(NSObject *)obj
{
UIView *view = nil;
if ([obj isKindOfClass:[UIView class]]) {
view = (UIView *)obj;
} else if ([obj isKindOfClass:[UIViewController class]]) {
view = ((UIViewController *)obj).view;
}
CGRect frame = view.frame;
CGPoint offset = CGPointZero;
if ([view isKindOfClass:[UIScrollView class]])
offset = ((UIScrollView *)view).contentOffset;
return @{
CODELESS_VIEW_TREE_TOP_KEY: @((int)frame.origin.y),
CODELESS_VIEW_TREE_LEFT_KEY: @((int)frame.origin.x),
CODELESS_VIEW_TREE_WIDTH_KEY: @((int)frame.size.width),
CODELESS_VIEW_TREE_HEIGHT_KEY: @((int)frame.size.height),
CODELESS_VIEW_TREE_OFFSET_X_KEY: @((int)offset.x),
CODELESS_VIEW_TREE_OFFSET_Y_KEY: @((int)offset.y),
CODELESS_VIEW_TREE_VISIBILITY_KEY: view.isHidden ? @4 : @0
};
}
@end
#endif

View File

@ -0,0 +1,37 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_SWIFT_NAME(CodelessParameterComponent)
@interface FBSDKCodelessParameterComponent : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, copy, readonly) NSString *value;
@property (nonatomic, readonly) NSArray *path;
@property (nonatomic, copy, readonly) NSString *pathType;
- (instancetype)initWithJSON:(NSDictionary *)dict;
@end
#endif

View File

@ -0,0 +1,50 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKCodelessParameterComponent.h"
#import "FBSDKCodelessPathComponent.h"
#import "FBSDKViewHierarchyMacros.h"
@implementation FBSDKCodelessParameterComponent
- (instancetype)initWithJSON:(NSDictionary *)dict {
if (self = [super init]) {
_name = [dict[CODELESS_MAPPING_PARAMETER_NAME_KEY] copy];
_value = [dict[CODELESS_MAPPING_PARAMETER_VALUE_KEY] copy];
_pathType = [dict[CODELESS_MAPPING_PATH_TYPE_KEY] copy];
NSArray *ary = dict[CODELESS_MAPPING_PATH_KEY];
NSMutableArray *mut = [NSMutableArray array];
for (NSDictionary *info in ary) {
FBSDKCodelessPathComponent *component = [[FBSDKCodelessPathComponent alloc] initWithJSON:info];
[mut addObject:component];
}
_path = [mut copy];
}
return self;
}
@end
#endif

View File

@ -0,0 +1,51 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
typedef NS_OPTIONS(int, FBSDKCodelessMatchBitmaskField)
{
FBSDKCodelessMatchBitmaskFieldID = 1,
FBSDKCodelessMatchBitmaskFieldText = 1 << 1,
FBSDKCodelessMatchBitmaskFieldTag = 1 << 2,
FBSDKCodelessMatchBitmaskFieldDescription = 1 << 3,
FBSDKCodelessMatchBitmaskFieldHint = 1 << 4
};
NS_SWIFT_NAME(CodelessPathComponent)
@interface FBSDKCodelessPathComponent : NSObject
@property (nonatomic, copy, readonly) NSString *className;
@property (nonatomic, copy, readonly) NSString *text;
@property (nonatomic, copy, readonly) NSString *hint;
@property (nonatomic, copy, readonly) NSString *desc; // description
@property (nonatomic, readonly) int index;
@property (nonatomic, readonly) int tag;
@property (nonatomic, readonly) int section;
@property (nonatomic, readonly) int row;
@property (nonatomic, readonly) int matchBitmask;
- (instancetype)initWithJSON:(NSDictionary*)dict;
@end
#endif

View File

@ -0,0 +1,64 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKCodelessPathComponent.h"
#import "FBSDKViewHierarchyMacros.h"
@implementation FBSDKCodelessPathComponent
- (instancetype)initWithJSON:(NSDictionary *)dict {
if (self = [super init]) {
_className = [dict[CODELESS_MAPPING_CLASS_NAME_KEY] copy];
_text = [dict[CODELESS_MAPPING_TEXT_KEY] copy];
_hint = [dict[CODELESS_MAPPING_HINT_KEY] copy];
_desc = [dict[CODELESS_MAPPING_DESC_KEY] copy];
if (dict[CODELESS_MAPPING_INDEX_KEY]) {
_index = [dict[CODELESS_MAPPING_INDEX_KEY] intValue];
} else {
_index = -1;
}
if (dict[CODELESS_MAPPING_SECTION_KEY]) {
_section = [dict[CODELESS_MAPPING_SECTION_KEY] intValue];
} else {
_section = -1;
}
if (dict[CODELESS_MAPPING_ROW_KEY]) {
_row = [dict[CODELESS_MAPPING_ROW_KEY] intValue];
} else {
_row = -1;
}
_tag = [dict[CODELESS_MAPPING_TAG_KEY] intValue];
_matchBitmask = [dict[CODELESS_MAPPING_MATCH_BITMASK_KEY] intValue];
}
return self;
}
@end
#endif

View File

@ -0,0 +1,42 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <UIKit/UIKit.h>
NS_SWIFT_NAME(EventBinding)
@interface FBSDKEventBinding : NSObject
@property (nonatomic, copy, readonly) NSString *eventName;
@property (nonatomic, copy, readonly) NSString *eventType;
@property (nonatomic, copy, readonly) NSString *appVersion;
@property (nonatomic, readonly) NSArray *path;
@property (nonatomic, copy, readonly) NSString *pathType;
@property (nonatomic, readonly) NSArray *parameters;
+ (BOOL)isViewMatchPath:(UIView *)view path:(NSArray *)path;
+ (BOOL)isPath:(NSArray *)path matchViewPath:(NSArray *)viewPath;
- (FBSDKEventBinding *)initWithJSON:(NSDictionary *)dict;
- (void)trackEvent:(id)sender;
@end
#endif

View File

@ -0,0 +1,282 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKEventBinding.h"
#import "FBSDKAppEvents.h"
#import "FBSDKAppEventsUtility.h"
#import "FBSDKCodelessParameterComponent.h"
#import "FBSDKCodelessPathComponent.h"
#import "FBSDKSwizzler.h"
#import "FBSDKUtility.h"
#import "FBSDKViewHierarchy.h"
#import "FBSDKViewHierarchyMacros.h"
#define CODELESS_PATH_TYPE_ABSOLUTE @"absolute"
#define CODELESS_PATH_TYPE_RELATIVE @"relative"
#define CODELESS_CODELESS_EVENT_KEY @"_is_fb_codeless"
#define PARAMETER_NAME_PRICE @"_valueToSum"
@implementation FBSDKEventBinding
- (FBSDKEventBinding *)initWithJSON:(NSDictionary *)dict
{
if ((self = [super init])) {
_eventName = [dict[CODELESS_MAPPING_EVENT_NAME_KEY] copy];
_eventType = [dict[CODELESS_MAPPING_EVENT_TYPE_KEY] copy];
_appVersion = [dict[CODELESS_MAPPING_APP_VERSION_KEY] copy];
_pathType = [dict[CODELESS_MAPPING_PATH_TYPE_KEY] copy];
NSArray *pathComponents = dict[CODELESS_MAPPING_PATH_KEY];
NSMutableArray *mut = [NSMutableArray array];
for (NSDictionary *info in pathComponents) {
FBSDKCodelessPathComponent *component = [[FBSDKCodelessPathComponent alloc] initWithJSON:info];
[mut addObject:component];
}
_path = [mut copy];
NSArray *parameters = dict[CODELESS_MAPPING_PARAMETERS_KEY];
mut = [NSMutableArray array];
for (NSDictionary *info in parameters) {
FBSDKCodelessParameterComponent *component = [[FBSDKCodelessParameterComponent alloc] initWithJSON:info];
[mut addObject:component];
}
_parameters = [mut copy];
}
return self;
}
- (void)trackEvent:(id)sender
{
UIView *sourceView = [sender isKindOfClass:[UIView class]] ? (UIView *)sender : nil;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[CODELESS_CODELESS_EVENT_KEY] = @"1";
for (FBSDKCodelessParameterComponent *component in self.parameters) {
NSString *text = component.value;
if (!text || text.length == 0) {
text = [FBSDKEventBinding findParameterOfPath:component.path
pathType:component.pathType
sourceView:sourceView];
}
if (text) {
if ([component.name isEqualToString:PARAMETER_NAME_PRICE]) {
NSNumber *value = [FBSDKAppEventsUtility getNumberValue:text];
params[component.name] = value;
} else {
params[component.name] = text;
}
}
}
[FBSDKAppEvents logEvent:_eventName parameters:[params copy]];
}
+ (BOOL)matchAnyView:(NSArray *)views
pathComponent:(FBSDKCodelessPathComponent *)component
{
for (NSObject *view in views) {
if ([self match:view pathComponent:component]) {
return YES;
}
}
return NO;
}
+ (BOOL)match:(NSObject *)view
pathComponent:(FBSDKCodelessPathComponent *)component
{
NSString *className = NSStringFromClass([view class]);
if (![className isEqualToString:component.className]) {
return NO;
}
if (component.index >= 0) {
NSObject *parent = [FBSDKViewHierarchy getParent:view];
if (parent) {
NSArray *children = [FBSDKViewHierarchy getChildren:[FBSDKViewHierarchy getParent:view]];
NSUInteger index = [children indexOfObject:view];
if (index == NSNotFound || index != component.index) {
return NO;
}
} else {
if (0 != component.index) {
return NO;
}
}
}
if ((component.matchBitmask & FBSDKCodelessMatchBitmaskFieldText) > 0) {
NSString *text = [FBSDKViewHierarchy getText:view];
BOOL match = ((text.length == 0 && component.text.length == 0)
|| [text isEqualToString:component.text]);
if (!match) {
return NO;
}
}
if ((component.matchBitmask & FBSDKCodelessMatchBitmaskFieldTag) > 0
&& [view isKindOfClass:[UIView class]]
&& component.tag != ((UIView *)view).tag) {
return NO;
}
if ((component.matchBitmask & FBSDKCodelessMatchBitmaskFieldHint) > 0) {
NSString *hint = [FBSDKViewHierarchy getHint:view];
BOOL match = ((hint.length == 0 && component.hint.length == 0)
|| [hint isEqualToString:component.hint]);
if (!match) {
return NO;
}
}
return YES;
}
+ (BOOL)isViewMatchPath:(UIView *)view path:(NSArray *)path
{
NSArray *viewPath = [FBSDKViewHierarchy getPath:view];
BOOL isMatch = [self isPath:path matchViewPath:viewPath];
return isMatch;
}
+ (BOOL)isPath:(NSArray *)path matchViewPath:(NSArray *)viewPath {
for (NSInteger i = 0; i < MIN(path.count, viewPath.count); i++) {
NSInteger idxPath = path.count - i - 1;
NSInteger idxViewPath = viewPath.count - i - 1;
FBSDKCodelessPathComponent *pathComponent = path[idxPath];
FBSDKCodelessPathComponent *viewPathComponent = viewPath[idxViewPath];
if (![pathComponent.className isEqualToString:viewPathComponent.className]) {
return NO;
}
if (pathComponent.index >= 0
&& pathComponent.index != viewPathComponent.index) {
return NO;
}
if ((pathComponent.matchBitmask & FBSDKCodelessMatchBitmaskFieldText) > 0) {
NSString *text = viewPathComponent.text;
BOOL match = ((text.length == 0 && pathComponent.text.length == 0)
|| [text isEqualToString:pathComponent.text]
|| [[FBSDKUtility SHA256Hash:text] isEqualToString:pathComponent.text]);
if (!match) {
return NO;
}
}
if ((pathComponent.matchBitmask & FBSDKCodelessMatchBitmaskFieldTag) > 0
&& pathComponent.tag != viewPathComponent.tag) {
return NO;
}
if ((pathComponent.matchBitmask & FBSDKCodelessMatchBitmaskFieldHint) > 0) {
NSString *hint = viewPathComponent.hint;
BOOL match = ((hint.length == 0 && pathComponent.hint.length == 0)
|| [hint isEqualToString:pathComponent.hint]
|| [[FBSDKUtility SHA256Hash:hint] isEqualToString:pathComponent.hint]);
if (!match) {
return NO;
}
}
}
return YES;
}
+ (NSObject *)findViewByPath:(NSArray *)path parent:(NSObject *)parent level:(int)level {
if (level >= path.count) {
return nil;
}
FBSDKCodelessPathComponent *pathComponent = path[level];
// If found parent, skip to next level
if ([pathComponent.className isEqualToString:CODELESS_MAPPING_PARENT_CLASS_NAME]) {
NSObject *nextParent = [FBSDKViewHierarchy getParent:parent];
return [FBSDKEventBinding findViewByPath:path parent:nextParent level:level + 1];
} else if ([pathComponent.className isEqualToString:CODELESS_MAPPING_CURRENT_CLASS_NAME]) {
return parent;
}
NSArray *children;
if (parent) {
children = [FBSDKViewHierarchy getChildren:parent];
} else {
UIWindow *window = [UIApplication sharedApplication].delegate.window;
if (window) {
children = @[window];
} else {
return nil;
}
}
if (path.count - 1 == level) {
int index = pathComponent.index;
if (index >= 0) {
NSObject *child = index < children.count ? children[index] : nil;
if ([self match:child pathComponent:pathComponent]) {
return child;
}
} else {
for (NSObject *child in children) {
if ([self match:child pathComponent:pathComponent]) {
return child;
}
}
}
} else {
for (NSObject *child in children) {
NSObject *result = [self findViewByPath:path parent:child level:level + 1];
if (result) {
return result;
}
}
}
return nil;
}
// MARK: - find event parameters via relative path
+ (NSString *)findParameterOfPath:(NSArray *)path
pathType:(NSString *)pathType
sourceView:(UIView *)sourceView {
if (0 == path.count) {
return nil;
}
UIView *rootView = sourceView;
if (![pathType isEqualToString:CODELESS_PATH_TYPE_RELATIVE]) {
rootView = nil;
}
NSObject *foundObj = [self findViewByPath:path parent:rootView level:0];
return [FBSDKViewHierarchy getText:foundObj];
}
@end
#endif

View File

@ -0,0 +1,34 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_SWIFT_NAME(EventBindingManager)
@interface FBSDKEventBindingManager : NSObject
- (FBSDKEventBindingManager*)initWithJSON:(NSDictionary*)dict;
- (void)updateBindings:(NSArray *)bindings;
+ (NSArray *)parseArray:(NSArray *)array;
@end
#endif

View File

@ -0,0 +1,397 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKEventBindingManager.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import "FBSDKCodelessPathComponent.h"
#import "FBSDKEventBinding.h"
#import "FBSDKSwizzler.h"
#import "FBSDKTypeUtility.h"
#import "FBSDKViewHierarchy.h"
#import "FBSDKViewHierarchyMacros.h"
#define ReactNativeTargetKey @"target"
#define ReactNativeTouchEndEventName @"touchEnd"
#define ReactNativeClassRCTTextView "RCTTextView"
#define ReactNativeClassRCTImageView "RCTImageVIew"
#define ReactNativeClassRCTTouchEvent "RCTTouchEvent"
#define ReactNativeClassRCTTouchHandler "RCTTouchHandler"
@interface FBSDKEventBindingManager ()
{
BOOL isStarted;
NSMutableDictionary *reactBindings;
NSSet *validClasses;
BOOL hasReactNative;
NSArray *eventBindings;
}
@end
@implementation FBSDKEventBindingManager
- (id)init {
self = [super init];
if (self) {
isStarted = NO;
hasReactNative = NO;
reactBindings = [NSMutableDictionary dictionary];
NSMutableSet *classes = [NSMutableSet set];
[classes addObject:[UIControl class]];
[classes addObject:[UITableView class]];
[classes addObject:[UICollectionView class]];
// ReactNative
Class classRCTRootView = objc_lookUpClass(ReactNativeClassRCTRootView);
if (classRCTRootView != nil) {
hasReactNative = YES;
Class classRCTView = objc_lookUpClass(ReactNativeClassRCTView);
Class classRCTTextView = objc_lookUpClass(ReactNativeClassRCTTextView);
Class classRCTImageView = objc_lookUpClass(ReactNativeClassRCTImageView);
if (classRCTView) {
[classes addObject:classRCTView];
}
if (classRCTTextView) {
[classes addObject:classRCTTextView];
}
if (classRCTImageView) {
[classes addObject:classRCTImageView];
}
}
validClasses = [NSSet setWithSet:classes];
}
return self;
}
+ (NSArray *)parseArray:(NSArray *)array {
NSMutableArray *result = [NSMutableArray array];
for (NSDictionary *json in array) {
FBSDKEventBinding *binding = [[FBSDKEventBinding alloc] initWithJSON:json];
[result addObject:binding];
}
return [result copy];
}
- (FBSDKEventBindingManager*)initWithJSON:(NSDictionary*)dict
{
if ((self = [super init])) {
NSArray *eventBindingsDict = [FBSDKTypeUtility arrayValue:dict[@"event_bindings"]];
NSMutableArray *bindings = [NSMutableArray array];
for (NSDictionary *d in eventBindingsDict) {
FBSDKEventBinding *e = [[FBSDKEventBinding alloc] initWithJSON:d];
[bindings addObject:e];
}
eventBindings = [bindings copy];
}
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
- (void)start
{
if (isStarted) {
return;
}
if (0 == eventBindings.count) {
return;
}
isStarted = YES;
void (^blockToWindow)(id view) = ^(id view) {
[self matchView:view delegate:nil];
};
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow)
onClass:[UIControl class]
withBlock:blockToWindow named:@"map_control"];
// ReactNative
if (hasReactNative) { // If app is built via ReactNative
Class classRCTView = objc_lookUpClass(ReactNativeClassRCTView);
Class classRCTTextView = objc_lookUpClass(ReactNativeClassRCTTextView);
Class classRCTImageView = objc_lookUpClass(ReactNativeClassRCTImageView);
Class classRCTTouchHandler = objc_lookUpClass(ReactNativeClassRCTTouchHandler);
// All react-native views would be added tp RCTRootView, so no need to check didMoveToWindow
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow)
onClass:classRCTView
withBlock:blockToWindow
named:@"match_react_native"];
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow)
onClass:classRCTTextView
withBlock:blockToWindow
named:@"match_react_native"];
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow)
onClass:classRCTImageView
withBlock:blockToWindow
named:@"match_react_native"];
// RCTTouchHandler handles with touch events, like touchEnd and uses RCTEventDispather to dispatch events, so we can check _updateAndDispatchTouches to fire events
[FBSDKSwizzler swizzleSelector:@selector(_updateAndDispatchTouches:eventName:) onClass:classRCTTouchHandler withBlock:^(id touchHandler, SEL command, id touches, id eventName){
if ([touches isKindOfClass:[NSSet class]] && [eventName isKindOfClass:[NSString class]]) {
@try {
NSString *reactEventName = (NSString *)eventName;
NSSet<UITouch *> *reactTouches = (NSSet<UITouch *> *)touches;
if ([reactEventName isEqualToString:ReactNativeTouchEndEventName]) {
for (UITouch *touch in reactTouches) {
UIView *targetView = ((UITouch *)touch).view.superview;
NSNumber *reactTag = nil;
// Find the closest React-managed touchable view like RCTTouchHandler
while(targetView) {
reactTag = [FBSDKViewHierarchy getViewReactTag:targetView];
if (reactTag != nil && targetView.userInteractionEnabled) {
break;
}
targetView = targetView.superview;
}
FBSDKEventBinding *eventBinding = self->reactBindings[reactTag];
if (reactTag != nil && eventBinding != nil) {
[eventBinding trackEvent:nil];
}
}
}
}
@catch(NSException *exception) {
// Catch exception here to prevent LytroKit from crashing app
}
}
} named:@"dispatch_rn_event"];
}
// UITableView
void (^tableViewBlock)(UITableView *tableView,
SEL cmd,
id<UITableViewDelegate> delegate) =
^(UITableView *tableView, SEL cmd, id<UITableViewDelegate> delegate) {
if (!delegate) {
return;
}
[self matchView:tableView delegate:delegate];
};
[FBSDKSwizzler swizzleSelector:@selector(setDelegate:)
onClass:[UITableView class]
withBlock:tableViewBlock
named:@"match_table_view"];
// UICollectionView
void (^collectionViewBlock)(UICollectionView *collectionView,
SEL cmd,
id<UICollectionViewDelegate> delegate) =
^(UICollectionView *collectionView, SEL cmd, id<UICollectionViewDelegate> delegate) {
if (nil == delegate) {
return;
}
[self matchView:collectionView delegate:delegate];
};
[FBSDKSwizzler swizzleSelector:@selector(setDelegate:)
onClass:[UICollectionView class]
withBlock:collectionViewBlock
named:@"handle_collection_view"];
}
- (void)rematchBindings {
if (0 == eventBindings.count) {
return;
}
NSArray *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows) {
[self matchSubviewsIn:window];
}
}
- (void)matchSubviewsIn:(UIView *)view {
if (!view) {
return;
}
for (UIView *subview in view.subviews) {
BOOL isValidClass = NO;
for (Class cls in validClasses) {
if ([subview isKindOfClass:cls]) {
isValidClass = YES;
break;
}
}
if (isValidClass) {
if ([subview isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)subview;
if (tableView.delegate) {
[self matchView:subview delegate:tableView.delegate];
}
} else if ([subview isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)subview;
if (collectionView.delegate) {
[self matchView:subview delegate:collectionView.delegate];
}
} else {
[self matchView:subview delegate:nil];
}
}
if (![subview isKindOfClass:[UIControl class]]) {
[self matchSubviewsIn:subview];
}
}
}
// check if the view is matched to any event
- (void)matchView:(UIView *)view delegate:(id)delegate {
if (0 == eventBindings.count) {
return;
}
fb_dispatch_on_main_thread(^{
if (![view window]) {
return;
}
NSArray *path = [FBSDKViewHierarchy getPath:view];
fb_dispatch_on_default_thread(^{
if ([view isKindOfClass:[UIControl class]]) {
UIControl *control = (UIControl *)view;
for (FBSDKEventBinding *binding in self->eventBindings) {
if ([FBSDKEventBinding isPath:binding.path matchViewPath:path]) {
fb_dispatch_on_main_thread(^{
[control addTarget:binding
action:@selector(trackEvent:)
forControlEvents:UIControlEventTouchUpInside];
});
break;
}
}
} else if (self->hasReactNative
&& [view respondsToSelector:@selector(reactTag)]) {
for (FBSDKEventBinding *binding in self->eventBindings) {
if ([FBSDKEventBinding isPath:binding.path matchViewPath:path]) {
fb_dispatch_on_main_thread(^{
if (view) {
NSNumber *reactTag = [FBSDKViewHierarchy getViewReactTag:view];
if (reactTag != nil) {
self->reactBindings[reactTag] = binding;
}
}
});
break;
}
}
} else if ([view isKindOfClass:[UITableView class]]
&& [delegate conformsToProtocol:@protocol(UITableViewDelegate)]) {
fb_dispatch_on_default_thread(^{
NSMutableSet *matchedBindings = [NSMutableSet set];
for (FBSDKEventBinding *binding in self->eventBindings) {
if (binding.path.count > 1) {
NSArray *shortPath = [binding.path
subarrayWithRange:NSMakeRange(0, binding.path.count - 1)];
if ([FBSDKEventBinding isPath:shortPath matchViewPath:path]) {
[matchedBindings addObject:binding];
}
}
}
if (matchedBindings.count > 0) {
NSArray *bindings = matchedBindings.allObjects;
void (^block)(id, SEL, id, id) = ^(id target, SEL command, UITableView *tableView, NSIndexPath *indexPath) {
fb_dispatch_on_main_thread(^{
for (FBSDKEventBinding *binding in bindings) {
FBSDKCodelessPathComponent *component = binding.path.lastObject;
if ((component.section == -1 || component.section == indexPath.section)
&& (component.row == -1 || component.row == indexPath.row)) {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[binding trackEvent:cell];
}
}
});
};
[FBSDKSwizzler swizzleSelector:@selector(tableView:didSelectRowAtIndexPath:)
onClass:[delegate class]
withBlock:block
named:@"handle_table_view"];
}
});
} else if ([view isKindOfClass:[UICollectionView class]]
&& [delegate conformsToProtocol:@protocol(UICollectionViewDelegate)]) {
fb_dispatch_on_default_thread(^{
NSMutableSet *matchedBindings = [NSMutableSet set];
for (FBSDKEventBinding *binding in self->eventBindings) {
if (binding.path.count > 1) {
NSArray *shortPath = [binding.path
subarrayWithRange:NSMakeRange(0, binding.path.count - 1)];
if ([FBSDKEventBinding isPath:shortPath matchViewPath:path]) {
[matchedBindings addObject:binding];
}
}
}
if (matchedBindings.count > 0) {
NSArray *bindings = matchedBindings.allObjects;
void (^block)(id, SEL, id, id) = ^(id target, SEL command, UICollectionView *collectionView, NSIndexPath *indexPath) {
fb_dispatch_on_main_thread(^{
for (FBSDKEventBinding *binding in bindings) {
FBSDKCodelessPathComponent *component = binding.path.lastObject;
if ((component.section == -1 || component.section == indexPath.section)
&& (component.row == -1 || component.row == indexPath.row)) {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[binding trackEvent:cell];
}
}
});
};
[FBSDKSwizzler swizzleSelector:@selector(collectionView:didSelectItemAtIndexPath:)
onClass:[delegate class]
withBlock:block
named:@"handle_collection_view"];
}
});
}
});
});
}
#pragma clang diagnostic pop
- (void)updateBindings:(NSArray *)bindings {
eventBindings = bindings;
[reactBindings removeAllObjects];
if (!isStarted) {
[self start];
}
fb_dispatch_on_main_thread(^{
[self rematchBindings];
});
}
@end
#endif

View File

@ -0,0 +1,34 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKEventDeactivationManager : NSObject
+ (void)enable;
+ (void)updateDeactivatedEvents:(nullable NSDictionary<NSString *, id> *)events;
+ (void)processEvents:(NSMutableArray<NSDictionary<NSString *, id> *> *)events;
+ (nullable NSDictionary<NSString *, id> *)processParameters:(nullable NSDictionary<NSString *, id> *)parameters
eventName:(NSString *)eventName;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,119 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKEventDeactivationManager.h"
static NSString *const DEPRECATED_PARAM_KEY = @"deprecated_param";
static NSString *const DEPRECATED_EVENT_KEY = @"is_deprecated_event";
@interface FBSDKDeactivatedEvent : NSObject
@property (nonatomic, readonly, copy) NSString *eventName;
@property (nonatomic, readonly, copy, nullable) NSSet<NSString *> *deactivatedParams;
-(instancetype)initWithEventName:(NSString *)eventName
deactivatedParams:(NSSet<NSString *> *)deactivatedParams;
@end
@implementation FBSDKDeactivatedEvent
-(instancetype)initWithEventName:(NSString *)eventName
deactivatedParams:(NSSet<NSString *> *)deactivatedParams
{
self = [super init];
if (self) {
_eventName = eventName;
_deactivatedParams = deactivatedParams;
}
return self;
}
@end
@implementation FBSDKEventDeactivationManager
static BOOL isEventDeactivationEnabled = NO;
static NSMutableSet<NSString *> *_deactivatedEvents;
static NSMutableArray<FBSDKDeactivatedEvent *> *_eventsWithDeactivatedParams;
+ (void)enable
{
isEventDeactivationEnabled = YES;
}
+ (void)updateDeactivatedEvents:(nullable NSDictionary<NSString *, id> *)events
{
if (!isEventDeactivationEnabled || events.count == 0) {
return;
}
[_deactivatedEvents removeAllObjects];
[_eventsWithDeactivatedParams removeAllObjects];
NSMutableArray<FBSDKDeactivatedEvent *> *deactivatedParamsArray = [NSMutableArray array];
NSMutableSet<NSString *> *deactivatedEventSet = [NSMutableSet set];
for (NSString *eventName in events.allKeys) {
NSDictionary<NSString *, id> *eventInfo = events[eventName];
if (!eventInfo) {
return;
}
if (eventInfo[DEPRECATED_EVENT_KEY]) {
[deactivatedEventSet addObject:eventName];
}
if (eventInfo[DEPRECATED_PARAM_KEY]) {
FBSDKDeactivatedEvent *eventWithDeactivatedParams = [[FBSDKDeactivatedEvent alloc] initWithEventName:eventName
deactivatedParams:[NSSet setWithArray:eventInfo[DEPRECATED_PARAM_KEY]]];
[deactivatedParamsArray addObject:eventWithDeactivatedParams];
}
}
_deactivatedEvents = deactivatedEventSet;
_eventsWithDeactivatedParams = deactivatedParamsArray;
}
+ (void)processEvents:(NSMutableArray<NSDictionary<NSString *, id> *> *)events
{
if (!isEventDeactivationEnabled) {
return;
}
NSArray<NSDictionary<NSString *, id> *> *eventArray = [events copy];
for (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *event in eventArray) {
if ([_deactivatedEvents containsObject:event[@"event"][@"_eventName"]]) {
[events removeObject:event];
}
}
}
+ (nullable NSDictionary<NSString *, id> *)processParameters:(nullable NSDictionary<NSString *, id> *)parameters
eventName:(NSString *)eventName
{
if (!isEventDeactivationEnabled || parameters.count == 0 || _eventsWithDeactivatedParams.count == 0) {
return parameters;
}
NSMutableDictionary<NSString *, id> *params = [NSMutableDictionary dictionaryWithDictionary:parameters];
for (NSString *key in [parameters keyEnumerator]) {
for (FBSDKDeactivatedEvent *event in _eventsWithDeactivatedParams) {
if ([event.eventName isEqualToString:eventName] && [event.deactivatedParams containsObject:key]) {
[params removeObjectForKey:key];
}
}
}
return [params copy];
}
@end

View File

@ -0,0 +1,245 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#if SWIFT_PACKAGE
#import "FBSDKAppEvents.h"
#else
#import <FBSDKCoreKit/FBSDKAppEvents.h>
#endif
#import "FBSDKAppEventsUtility.h"
@class FBSDKGraphRequest;
// Internally known event names
/** Use to log that the share dialog was launched */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameShareSheetLaunch;
/** Use to log that the share dialog was dismissed */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameShareSheetDismiss;
/** Use to log that the permissions UI was launched */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNamePermissionsUILaunch;
/** Use to log that the permissions UI was dismissed */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNamePermissionsUIDismiss;
/** Use to log that the login view was used */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameLoginViewUsage;
/** Use to log that the share tray launched. */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameShareTrayDidLaunch;
/** Use to log that the person selected a sharing target. */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameShareTrayDidSelectActivity;
// Internally known event parameters
/** String parameter specifying the outcome of a dialog invocation */
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterDialogOutcome;
/** Parameter key used to specify which application launches this application. */
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLaunchSource;
/** Use to log the result of a call to FBDialogs presentShareDialogWithParams: */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentShareDialog;
/** Use to log the result of a call to FBDialogs presentShareDialogWithOpenGraphActionParams: */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentShareDialogOG;
/** Use to log the result of a call to FBDialogs presentLikeDialogWithLikeParams: */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentLikeDialogOG;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentShareDialogPhoto;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentMessageDialog;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentMessageDialogPhoto;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsPresentMessageDialogOG;
/** Use to log the start of an auth request that cannot be fulfilled by the token cache */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSessionAuthStart;
/** Use to log the end of an auth request that was not fulfilled by the token cache */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSessionAuthEnd;
/** Use to log the start of a specific auth method as part of an auth request */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSessionAuthMethodStart;
/** Use to log the end of the last tried auth method as part of an auth request */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSessionAuthMethodEnd;
/** Use to log the timestamp for the transition to the Facebook native login dialog */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsNativeLoginDialogStart;
/** Use to log the timestamp for the transition back to the app after the Facebook native login dialog */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsNativeLoginDialogEnd;
/** Use to log the e2e timestamp metrics for web login */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBDialogsWebLoginCompleted;
/** Use to log the result of the App Switch OS AlertView. Only available on OS >= iOS10 */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSessionFASLoginDialogResult;
/** Use to log the live streaming events from sdk */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingStart;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingStop;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingPause;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingResume;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingError;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingUpdateStatus;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingVideoID;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingMic;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingCamera;
/** Use to log the results of a share dialog */
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKEventShareDialogResult;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKEventMessengerShareDialogResult;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKEventAppInviteShareDialogResult;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKEventShareDialogShow;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKEventMessengerShareDialogShow;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKEventAppInviteShareDialogShow;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterDialogMode;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterDialogShareContentType;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterDialogShareContentUUID;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterDialogShareContentPageID;
/** Use to log parameters for share tray use */
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterShareTrayActivityName;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterShareTrayResult;
/** Use to log parameters for live streaming*/
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLiveStreamingPrevStatus;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLiveStreamingStatus;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLiveStreamingError;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLiveStreamingVideoID;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLiveStreamingMicEnabled;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLiveStreamingCameraEnabled;
// Internally known event parameter values
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogOutcomeValue_Completed;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogOutcomeValue_Cancelled;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogOutcomeValue_Failed;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeOpenGraph;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeStatus;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypePhoto;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeVideo;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeCamera;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeMessengerGenericTemplate;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeMessengerMediaTemplate;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeMessengerOpenGraphMusicTemplate;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareContentTypeUnknown;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeAutomatic;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeBrowser;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeNative;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeShareSheet;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeWeb;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeFeedBrowser;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeFeedWeb;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsDialogShareModeUnknown;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsNativeLoginDialogStartTime;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsNativeLoginDialogEndTime;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWebLoginE2E;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeButtonImpression;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLoginButtonImpression;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKSendButtonImpression;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKShareButtonImpression;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingButtonImpression;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKSmartLoginService;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeButtonDidTap;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLoginButtonDidTap;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKSendButtonDidTap;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKShareButtonDidTap;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLiveStreamingButtonDidTap;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlDidDisable;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlDidLike;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlDidPresentDialog;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlDidTap;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlDidUnlike;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlError;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlImpression;
FOUNDATION_EXPORT NSString *const FBSDKAppEventNameFBSDKLikeControlNetworkUnavailable;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterDialogErrorMessage;
FOUNDATION_EXPORT NSString *const FBSDKAppEventParameterLogTime;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesHandlerKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesActionKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesEventKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesParamsKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesPixelTrackKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesPixelTrackCustomKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesPixelTrackSingleKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesPixelTrackSingleCustomKey;
FOUNDATION_EXPORT NSString *const FBSDKAppEventsWKWebViewMessagesPixelIDKey;
@interface FBSDKAppEvents (Internal)
@property (class, nonatomic, strong, readonly) FBSDKAppEvents *singleton;
#ifdef DEBUG
+ (void)resetSingleton;
#endif
+ (void)logInternalEvent:(FBSDKAppEventName)eventName
isImplicitlyLogged:(BOOL)isImplicitlyLogged;
+ (void)logInternalEvent:(FBSDKAppEventName)eventName
valueToSum:(double)valueToSum
isImplicitlyLogged:(BOOL)isImplicitlyLogged;
+ (void)logInternalEvent:(FBSDKAppEventName)eventName
parameters:(NSDictionary *)parameters
isImplicitlyLogged:(BOOL)isImplicitlyLogged;
+ (void)logInternalEvent:(FBSDKAppEventName)eventName
parameters:(NSDictionary *)parameters
isImplicitlyLogged:(BOOL)isImplicitlyLogged
accessToken:(FBSDKAccessToken *)accessToken;
+ (void)logInternalEvent:(FBSDKAppEventName)eventName
valueToSum:(double)valueToSum
parameters:(NSDictionary *)parameters
isImplicitlyLogged:(BOOL)isImplicitlyLogged;
+ (void)logInternalEvent:(NSString *)eventName
valueToSum:(NSNumber *)valueToSum
parameters:(NSDictionary *)parameters
isImplicitlyLogged:(BOOL)isImplicitlyLogged
accessToken:(FBSDKAccessToken *)accessToken;
+ (void)logImplicitEvent:(NSString *)eventName
valueToSum:(NSNumber *)valueToSum
parameters:(NSDictionary *)parameters
accessToken:(FBSDKAccessToken *)accessToken;
- (void)flushForReason:(FBSDKAppEventsFlushReason)flushReason;
- (void)registerNotifications;
@end

View File

@ -0,0 +1,26 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_SWIFT_NAME(AppEventsDeviceInfo)
@interface FBSDKAppEventsDeviceInfo : NSObject
+ (void)extendDictionaryWithDeviceInfo:(NSMutableDictionary *)dictionary;
@end

View File

@ -0,0 +1,279 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKAppEventsDeviceInfo.h"
#import <sys/sysctl.h>
#import <sys/utsname.h>
#if !TARGET_OS_TV
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "FBSDKAppEvents+Internal.h"
#import "FBSDKDynamicFrameworkLoader.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKUtility.h"
#define FB_ARRAY_COUNT(x) sizeof(x) / sizeof(x[0])
static const u_int FB_GROUP1_RECHECK_DURATION = 30 * 60; // seconds
// Apple reports storage in binary gigabytes (1024^3) in their About menus, etc.
static const u_int FB_GIGABYTE = 1024 * 1024 * 1024; // bytes
@implementation FBSDKAppEventsDeviceInfo
{
// Ephemeral data, may change during the lifetime of an app. We collect them in different
// 'group' frequencies - group1 may gets collected once every 30 minutes.
// group1
NSString *_carrierName;
NSString *_timeZoneAbbrev;
unsigned long long _remainingDiskSpaceGB;
NSString *_timeZoneName;
// Persistent data, but we maintain it to make rebuilding the device info as fast as possible.
NSString *_bundleIdentifier;
NSString *_longVersion;
NSString *_shortVersion;
NSString *_sysVersion;
NSString *_machine;
NSString *_language;
unsigned long long _totalDiskSpaceGB;
unsigned long long _coreCount;
CGFloat _width;
CGFloat _height;
CGFloat _density;
// Other state
long _lastGroup1CheckTime;
BOOL _isEncodingDirty;
NSString *_encodedDeviceInfo;
}
#pragma mark - Public Methods
+ (void)extendDictionaryWithDeviceInfo:(NSMutableDictionary *)dictionary
{
dictionary[@"extinfo"] = [[self sharedDeviceInfo] encodedDeviceInfo];
}
#pragma mark - Internal Methods
+ (void)initialize
{
if (self == [FBSDKAppEventsDeviceInfo class]) {
[[self sharedDeviceInfo] _collectPersistentData];
}
}
+ (instancetype)sharedDeviceInfo
{
static FBSDKAppEventsDeviceInfo *_sharedDeviceInfo = nil;
if (_sharedDeviceInfo == nil) {
_sharedDeviceInfo = [[FBSDKAppEventsDeviceInfo alloc] init];
}
return _sharedDeviceInfo;
}
- (instancetype)init
{
if ((self = [super init])) {
_isEncodingDirty = YES;
}
return self;
}
- (NSString *)encodedDeviceInfo
{
@synchronized (self) {
BOOL isGroup1Expired = [self _isGroup1Expired];
BOOL isEncodingExpired = isGroup1Expired; // Can || other groups in if we add them
// As long as group1 hasn't expired, we can just return the last generated value
if (_encodedDeviceInfo && !isEncodingExpired) {
return _encodedDeviceInfo;
}
if (isGroup1Expired) {
[self _collectGroup1Data];
}
if (_isEncodingDirty) {
self.encodedDeviceInfo = [self _generateEncoding];
_isEncodingDirty = NO;
}
return _encodedDeviceInfo;
}
}
- (void)setEncodedDeviceInfo:(NSString *)encodedDeviceInfo
{
@synchronized (self) {
if (![_encodedDeviceInfo isEqualToString:encodedDeviceInfo]) {
_encodedDeviceInfo = [encodedDeviceInfo copy];
}
}
}
// This data need only be collected once.
- (void)_collectPersistentData
{
// Bundle stuff
NSBundle *mainBundle = [NSBundle mainBundle];
_bundleIdentifier = mainBundle.bundleIdentifier;
_longVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"];
_shortVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
// Locale stuff
_language = [NSLocale currentLocale].localeIdentifier;
// Device stuff
UIDevice *device = [UIDevice currentDevice];
_sysVersion = device.systemVersion;
_coreCount = [FBSDKAppEventsDeviceInfo _coreCount];
UIScreen *sc = [UIScreen mainScreen];
CGRect sr = sc.bounds;
_width = sr.size.width;
_height = sr.size.height;
_density = sc.scale;
struct utsname systemInfo;
uname(&systemInfo);
_machine = @(systemInfo.machine);
// Disk space stuff
float totalDiskSpace = [FBSDKAppEventsDeviceInfo _getTotalDiskSpace].floatValue;
_totalDiskSpaceGB = (unsigned long long)round(totalDiskSpace / FB_GIGABYTE);
}
- (BOOL)_isGroup1Expired
{
return ([FBSDKAppEventsUtility unixTimeNow] - _lastGroup1CheckTime) > FB_GROUP1_RECHECK_DURATION;
}
// This data is collected only once every GROUP1_RECHECK_DURATION.
- (void)_collectGroup1Data
{
// Carrier
NSString *newCarrierName = [FBSDKAppEventsDeviceInfo _getCarrier];
if (![newCarrierName isEqualToString:_carrierName]) {
_carrierName = newCarrierName;
_isEncodingDirty = YES;
}
// Time zone
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
NSString *timeZoneName = timeZone.name;
if (![timeZoneName isEqualToString:_timeZoneName]) {
_timeZoneName = timeZoneName;
_timeZoneAbbrev = timeZone.abbreviation;
_isEncodingDirty = YES;
}
// Remaining disk space
float remainingDiskSpace = [FBSDKAppEventsDeviceInfo _getRemainingDiskSpace].floatValue;
unsigned long long newRemainingDiskSpaceGB = (unsigned long long)round(remainingDiskSpace / FB_GIGABYTE);
if (_remainingDiskSpaceGB != newRemainingDiskSpaceGB) {
_remainingDiskSpaceGB = newRemainingDiskSpaceGB;
_isEncodingDirty = YES;
}
_lastGroup1CheckTime = [FBSDKAppEventsUtility unixTimeNow];
}
- (NSString *)_generateEncoding
{
// Keep a bit of precision on density as it's the most likely to become non-integer.
NSString *densityString = _density ? [NSString stringWithFormat:@"%.02f", _density] : @"";
NSArray *arr = @[
@"i2", // version - starts with 'i' for iOS, we'll use 'a' for Android
_bundleIdentifier ?: @"",
_longVersion ?: @"",
_shortVersion ?: @"",
_sysVersion ?: @"",
_machine ?: @"",
_language ?: @"",
_timeZoneAbbrev ?: @"",
_carrierName ?: @"",
_width ? @((unsigned long)_width) : @"",
_height ? @((unsigned long)_height) : @"",
densityString,
@(_coreCount) ?: @"",
@(_totalDiskSpaceGB) ?: @"",
@(_remainingDiskSpaceGB) ?: @"",
_timeZoneName ?: @""
];
return [FBSDKBasicUtility JSONStringForObject:arr error:NULL invalidObjectHandler:NULL];
}
#pragma mark - Helper Methods
+ (NSNumber *)_getTotalDiskSpace
{
NSDictionary *attrs = [[[NSFileManager alloc] init] attributesOfFileSystemForPath:NSHomeDirectory()
error:nil];
return attrs[NSFileSystemSize];
}
+ (NSNumber *)_getRemainingDiskSpace
{
NSDictionary *attrs = [[[NSFileManager alloc] init] attributesOfFileSystemForPath:NSHomeDirectory()
error:nil];
return attrs[NSFileSystemFreeSize];
}
+ (uint)_coreCount
{
return [FBSDKAppEventsDeviceInfo _readSysCtlUInt:CTL_HW type:HW_AVAILCPU];
}
+ (uint)_readSysCtlUInt:(int)ctl type:(int)type
{
int mib[2] = {ctl, type};
uint value;
size_t size = sizeof value;
if (0 != sysctl(mib, FB_ARRAY_COUNT(mib), &value, &size, NULL, 0)) {
return 0;
}
return value;
}
+ (NSString *)_getCarrier
{
#if TARGET_OS_TV || TARGET_IPHONE_SIMULATOR
return @"NoCarrier";
#else
// Dynamically load class for this so calling app doesn't need to link framework in.
CTTelephonyNetworkInfo *networkInfo = [[fbsdkdfl_CTTelephonyNetworkInfoClass() alloc] init];
CTCarrier *carrier = networkInfo.subscriberCellularProvider;
return carrier.carrierName ?: @"NoCarrier";
#endif
}
@end

View File

@ -0,0 +1,42 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
// this type is not thread safe.
NS_SWIFT_NAME(AppEventsState)
@interface FBSDKAppEventsState : NSObject<NSCopying, NSSecureCoding>
@property (nonatomic, readonly, copy) NSArray *events;
@property (nonatomic, readonly, assign) NSUInteger numSkipped;
@property (nonatomic, readonly, copy) NSString *tokenString;
@property (nonatomic, readonly, copy) NSString *appID;
@property (nonatomic, readonly, getter=areAllEventsImplicit) BOOL allEventsImplicit;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithToken:(NSString *)tokenString appID:(NSString *)appID NS_DESIGNATED_INITIALIZER;
- (void)addEvent:(NSDictionary *)eventDictionary isImplicit:(BOOL)isImplicit;
- (void)addEventsFromAppEventState:(FBSDKAppEventsState *)appEventsState;
- (BOOL)isCompatibleWithAppEventsState:(FBSDKAppEventsState *)appEventsState;
- (BOOL)isCompatibleWithTokenString:(NSString *)tokenString appID:(NSString *)appID;
- (NSString *)JSONStringForEvents:(BOOL)includeImplicitEvents;
- (NSString *)extractReceiptData;
@end

View File

@ -0,0 +1,185 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKAppEventsState.h"
#import "FBSDKBasicUtility.h"
#import "FBSDKEventDeactivationManager.h"
#import "FBSDKRestrictiveDataFilterManager.h"
#define FBSDK_APPEVENTSTATE_ISIMPLICIT_KEY @"isImplicit"
#define FBSDK_APPEVENTSSTATE_MAX_EVENTS 1000
#define FBSDK_APPEVENTSSTATE_APPID_KEY @"appID"
#define FBSDK_APPEVENTSSTATE_EVENTS_KEY @"events"
#define FBSDK_APPEVENTSSTATE_NUMSKIPPED_KEY @"numSkipped"
#define FBSDK_APPEVENTSSTATE_TOKENSTRING_KEY @"tokenString"
#define FBSDK_APPEVENTSTATE_RECEIPTDATA_KEY @"receipt_data"
#define FBSDK_APPEVENTSTATE_RECEIPTID_KEY @"receipt_id"
@implementation FBSDKAppEventsState
{
NSMutableArray *_mutableEvents;
}
- (instancetype)initWithToken:(NSString *)tokenString appID:(NSString *)appID
{
if ((self = [super init])) {
_tokenString = [tokenString copy];
_appID = [appID copy];
_mutableEvents = [NSMutableArray array];
}
return self;
}
- (instancetype)copyWithZone:(NSZone *)zone
{
FBSDKAppEventsState *copy = [[FBSDKAppEventsState allocWithZone:zone] initWithToken:_tokenString appID:_appID];
if (copy) {
[copy->_mutableEvents addObjectsFromArray:_mutableEvents];
copy->_numSkipped = _numSkipped;
}
return copy;
}
#pragma mark - NSCoding
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (id)initWithCoder:(NSCoder *)decoder
{
NSString *appID = [decoder decodeObjectOfClass:[NSString class] forKey:FBSDK_APPEVENTSSTATE_APPID_KEY];
NSString *tokenString = [decoder decodeObjectOfClass:[NSString class] forKey:FBSDK_APPEVENTSSTATE_TOKENSTRING_KEY];
NSArray *events = [decoder decodeObjectOfClass:[NSArray class] forKey:FBSDK_APPEVENTSSTATE_EVENTS_KEY];
NSUInteger numSkipped = [[decoder decodeObjectOfClass:[NSNumber class] forKey:FBSDK_APPEVENTSSTATE_NUMSKIPPED_KEY] unsignedIntegerValue];
if ((self = [self initWithToken:tokenString appID:appID])) {
_mutableEvents = [NSMutableArray arrayWithArray:events];
_numSkipped = numSkipped;
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:_appID forKey:FBSDK_APPEVENTSSTATE_APPID_KEY];
[encoder encodeObject:_tokenString forKey:FBSDK_APPEVENTSSTATE_TOKENSTRING_KEY];
[encoder encodeObject:@(_numSkipped) forKey:FBSDK_APPEVENTSSTATE_NUMSKIPPED_KEY];
[encoder encodeObject:_mutableEvents forKey:FBSDK_APPEVENTSSTATE_EVENTS_KEY];
}
#pragma mark - Implementation
- (NSArray *)events
{
return [_mutableEvents copy];
}
- (void)addEventsFromAppEventState:(FBSDKAppEventsState *)appEventsState
{
NSArray *toAdd = appEventsState->_mutableEvents;
NSInteger excess = _mutableEvents.count + toAdd.count - FBSDK_APPEVENTSSTATE_MAX_EVENTS;
if (excess > 0) {
NSInteger range = FBSDK_APPEVENTSSTATE_MAX_EVENTS - _mutableEvents.count;
toAdd = [toAdd subarrayWithRange:NSMakeRange(0, range)];
_numSkipped += excess;
}
[_mutableEvents addObjectsFromArray:toAdd];
}
- (void)addEvent:(NSDictionary *)eventDictionary
isImplicit:(BOOL)isImplicit {
if (_mutableEvents.count >= FBSDK_APPEVENTSSTATE_MAX_EVENTS) {
_numSkipped++;
} else {
[_mutableEvents addObject:@{
@"event" : [eventDictionary mutableCopy],
FBSDK_APPEVENTSTATE_ISIMPLICIT_KEY : @(isImplicit)
}];
}
}
- (NSString *)extractReceiptData {
NSMutableString *receipts_string = [NSMutableString string];
NSInteger transactionId = 1;
for (NSMutableDictionary* events in _mutableEvents) {
NSMutableDictionary *event = events[@"event"];
NSString* receipt = event[@"receipt_data"];
// Add receipt id as the identifier for receipt data in event parameter.
// Receipt data will be sent as post parameter rather than the event parameter
if (receipt) {
NSString* idKey = [NSString stringWithFormat:@"receipt_%ld", (long)transactionId];
event[FBSDK_APPEVENTSTATE_RECEIPTID_KEY] = idKey;
NSString* receiptWithId = [NSString stringWithFormat:@"%@::%@;;;", idKey, receipt];
[receipts_string appendString:receiptWithId];
transactionId++;
}
}
return receipts_string;
}
- (BOOL)areAllEventsImplicit
{
for (NSDictionary *event in _mutableEvents) {
if (![[event valueForKey:FBSDK_APPEVENTSTATE_ISIMPLICIT_KEY] boolValue]) {
return NO;
}
}
return YES;
}
- (BOOL)isCompatibleWithAppEventsState:(FBSDKAppEventsState *)appEventsState
{
return ([self isCompatibleWithTokenString:appEventsState.tokenString appID:appEventsState.appID]);
}
- (BOOL)isCompatibleWithTokenString:(NSString *)tokenString appID:(NSString *)appID
{
// token strings can be nil (e.g., no user token) but appIDs should not.
BOOL tokenCompatible = ([self.tokenString isEqualToString:tokenString] ||
(self.tokenString == nil && tokenString == nil));
return (tokenCompatible &&
[self.appID isEqualToString:appID]);
}
- (NSString *)JSONStringForEvents:(BOOL)includeImplicitEvents
{
[FBSDKEventDeactivationManager processEvents:_mutableEvents];
NSMutableArray *events = [[NSMutableArray alloc] initWithCapacity:_mutableEvents.count];
for (NSDictionary *eventAndImplicitFlag in _mutableEvents) {
if (!includeImplicitEvents && [eventAndImplicitFlag[FBSDK_APPEVENTSTATE_ISIMPLICIT_KEY] boolValue]) {
continue;
}
NSMutableDictionary *event = eventAndImplicitFlag[@"event"];
NSAssert(event != nil, @"event cannot be nil");
[event removeObjectForKey:FBSDK_APPEVENTSTATE_RECEIPTDATA_KEY];
[events addObject:event];
}
return [FBSDKBasicUtility JSONStringForObject:events error:NULL invalidObjectHandler:NULL];
}
@end

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
@class FBSDKAppEventsState;
NS_SWIFT_NAME(AppEventsStateManager)
@interface FBSDKAppEventsStateManager : NSObject
+ (void)clearPersistedAppEventsStates;
// reads all saved event states, appends the param, and writes them all.
+ (void)persistAppEventsData:(FBSDKAppEventsState *)appEventsState;
// returns the array of saved app event states and deletes them.
+ (NSArray *)retrievePersistedAppEventsStates;
@end

View File

@ -0,0 +1,79 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKAppEventsStateManager.h"
#import <Foundation/Foundation.h>
#import "FBSDKAppEventsState.h"
#import "FBSDKAppEventsUtility.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKLogger.h"
#import "FBSDKSettings.h"
// A quick optimization to allow returning empty array if we know there are no persisted events.
static BOOL g_canSkipDiskCheck = NO;
@implementation FBSDKAppEventsStateManager
+ (void)clearPersistedAppEventsStates
{
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
logEntry:@"FBSDKAppEvents Persist: Clearing"];
[[NSFileManager defaultManager] removeItemAtPath:[[self class] filePath]
error:NULL];
g_canSkipDiskCheck = YES;
}
+ (void)persistAppEventsData:(FBSDKAppEventsState *)appEventsState
{
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
formatString:@"FBSDKAppEvents Persist: Writing %lu events", (unsigned long)appEventsState.events.count];
if (!appEventsState.events.count) {
return;
}
NSMutableArray *existingEvents = [NSMutableArray arrayWithArray:[[self class] retrievePersistedAppEventsStates]];
[existingEvents addObject:appEventsState];
[NSKeyedArchiver archiveRootObject:existingEvents toFile:[[self class] filePath]];
g_canSkipDiskCheck = NO;
}
+ (NSArray *)retrievePersistedAppEventsStates
{
NSMutableArray *eventsStates = [NSMutableArray array];
if (!g_canSkipDiskCheck) {
[eventsStates addObjectsFromArray:[NSKeyedUnarchiver unarchiveObjectWithFile:[[self class] filePath]]];
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
formatString:@"FBSDKAppEvents Persist: Read %lu event states. First state has %lu events",
(unsigned long)eventsStates.count,
(unsigned long)(eventsStates.count > 0 ? ((FBSDKAppEventsState *)eventsStates[0]).events.count : 0)];
[[self class] clearPersistedAppEventsStates];
}
return eventsStates;
}
#pragma mark - Private Helpers
+ (NSString *)filePath
{
return [FBSDKBasicUtility persistenceFilePath:@"com-facebook-sdk-AppEventsPersistedEvents.json"];
}
@end

View File

@ -0,0 +1,66 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
@class FBSDKAccessToken;
typedef NS_ENUM(NSUInteger, FBSDKAdvertisingTrackingStatus)
{
FBSDKAdvertisingTrackingAllowed,
FBSDKAdvertisingTrackingDisallowed,
FBSDKAdvertisingTrackingUnspecified
} NS_SWIFT_NAME(AppEventsUtility.AdvertisingTrackingStatus);
typedef NS_ENUM(NSUInteger, FBSDKAppEventsFlushReason)
{
FBSDKAppEventsFlushReasonExplicit,
FBSDKAppEventsFlushReasonTimer,
FBSDKAppEventsFlushReasonSessionChange,
FBSDKAppEventsFlushReasonPersistedEvents,
FBSDKAppEventsFlushReasonEventThreshold,
FBSDKAppEventsFlushReasonEagerlyFlushingEvent
} NS_SWIFT_NAME(AppEventsUtility.FlushReason);
NS_SWIFT_NAME(AppEventsUtility)
@interface FBSDKAppEventsUtility : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@property (class, nonatomic, copy, readonly) NSString *advertiserID;
@property (class, nonatomic, assign, readonly) FBSDKAdvertisingTrackingStatus advertisingTrackingStatus;
@property (class, nonatomic, strong, readonly) NSString *attributionID;
@property (class, nonatomic, assign, readonly) long unixTimeNow;
@property (class, nonatomic, assign, readonly) BOOL isDebugBuild;
+ (NSMutableDictionary *)activityParametersDictionaryForEvent:(NSString *)eventCategory
implicitEventsOnly:(BOOL)implicitEventsOnly
shouldAccessAdvertisingID:(BOOL)shouldAccessAdvertisingID;
+ (void)ensureOnMainThread:(NSString *)methodName className:(NSString *)className;
+ (NSString *)flushReasonToString:(FBSDKAppEventsFlushReason)flushReason;
+ (void)logAndNotify:(NSString *)msg allowLogAsDeveloperError:(BOOL)allowLogAsDeveloperError;
+ (void)logAndNotify:(NSString *)msg;
+ (NSString *)tokenStringToUseFor:(FBSDKAccessToken *)token;
+ (BOOL)validateIdentifier:(NSString *)identifier;
+ (id)getVariable:(NSString *)variableName fromInstance:(NSObject *)instance;
+ (NSNumber *)getNumberValue:(NSString *)text;
+ (BOOL)isSensitiveUserData:(NSString *)text;
@end

View File

@ -0,0 +1,430 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKAppEventsUtility.h"
#import <objc/runtime.h>
#import <AdSupport/AdSupport.h>
#import "FBSDKAccessToken.h"
#import "FBSDKAppEvents.h"
#import "FBSDKAppEventsDeviceInfo.h"
#import "FBSDKConstants.h"
#import "FBSDKDynamicFrameworkLoader.h"
#import "FBSDKError.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKLogger.h"
#import "FBSDKSettings.h"
#import "FBSDKTimeSpentData.h"
#define FBSDK_APPEVENTSUTILITY_ANONYMOUSIDFILENAME @"com-facebook-sdk-PersistedAnonymousID.json"
#define FBSDK_APPEVENTSUTILITY_ANONYMOUSID_KEY @"anon_id"
#define FBSDK_APPEVENTSUTILITY_MAX_IDENTIFIER_LENGTH 40
@implementation FBSDKAppEventsUtility
+ (NSMutableDictionary *)activityParametersDictionaryForEvent:(NSString *)eventCategory
implicitEventsOnly:(BOOL)implicitEventsOnly
shouldAccessAdvertisingID:(BOOL)shouldAccessAdvertisingID {
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[@"event"] = eventCategory;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
NSString *attributionID = [[self class] attributionID]; // Only present on iOS 6 and below.
[FBSDKBasicUtility dictionary:parameters setObject:attributionID forKey:@"attribution"];
#endif
if (!implicitEventsOnly && shouldAccessAdvertisingID) {
NSString *advertiserID = [[self class] advertiserID];
[FBSDKBasicUtility dictionary:parameters setObject:advertiserID forKey:@"advertiser_id"];
}
parameters[FBSDK_APPEVENTSUTILITY_ANONYMOUSID_KEY] = [FBSDKBasicUtility anonymousID];
FBSDKAdvertisingTrackingStatus advertisingTrackingStatus = [[self class] advertisingTrackingStatus];
if (advertisingTrackingStatus != FBSDKAdvertisingTrackingUnspecified) {
BOOL allowed = (advertisingTrackingStatus == FBSDKAdvertisingTrackingAllowed);
parameters[@"advertiser_tracking_enabled"] = @(allowed).stringValue;
}
if (advertisingTrackingStatus == FBSDKAdvertisingTrackingAllowed) {
NSString *userData = [FBSDKAppEvents getUserData];
if (userData){
parameters[@"ud"] = userData;
}
}
parameters[@"application_tracking_enabled"] = @(!FBSDKSettings.limitEventAndDataUsage).stringValue;
NSString *userID = [FBSDKAppEvents userID];
if (userID) {
parameters[@"app_user_id"] = userID;
}
[FBSDKAppEventsDeviceInfo extendDictionaryWithDeviceInfo:parameters];
static dispatch_once_t fetchBundleOnce;
static NSMutableArray *urlSchemes;
dispatch_once(&fetchBundleOnce, ^{
NSBundle *mainBundle = [NSBundle mainBundle];
urlSchemes = [[NSMutableArray alloc] init];
for (NSDictionary<NSString *, id> *fields in [mainBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]) {
NSArray<NSString *> *schemesForType = fields[@"CFBundleURLSchemes"];
if (schemesForType) {
[urlSchemes addObjectsFromArray:schemesForType];
}
}
});
if (urlSchemes.count > 0) {
parameters[@"url_schemes"] = [FBSDKBasicUtility JSONStringForObject:urlSchemes error:NULL invalidObjectHandler:NULL];
}
return parameters;
}
+ (NSString *)advertiserID
{
if (!FBSDKSettings.isAdvertiserIDCollectionEnabled) {
return nil;
}
NSString *result = nil;
Class ASIdentifierManagerClass = fbsdkdfl_ASIdentifierManagerClass();
if ([ASIdentifierManagerClass class]) {
ASIdentifierManager *manager = [ASIdentifierManagerClass sharedManager];
result = manager.advertisingIdentifier.UUIDString;
}
return result;
}
+ (FBSDKAdvertisingTrackingStatus)advertisingTrackingStatus
{
static dispatch_once_t fetchAdvertisingTrackingStatusOnce;
static FBSDKAdvertisingTrackingStatus status;
dispatch_once(&fetchAdvertisingTrackingStatusOnce, ^{
status = FBSDKAdvertisingTrackingUnspecified;
Class ASIdentifierManagerClass = fbsdkdfl_ASIdentifierManagerClass();
if ([ASIdentifierManagerClass class]) {
ASIdentifierManager *manager = [ASIdentifierManagerClass sharedManager];
if (manager) {
status = manager.advertisingTrackingEnabled ? FBSDKAdvertisingTrackingAllowed : FBSDKAdvertisingTrackingDisallowed;
}
}
});
return status;
}
+ (NSString *)attributionID
{
#if TARGET_OS_TV
return nil;
#else
return [UIPasteboard pasteboardWithName:@"fb_app_attribution" create:NO].string;
#endif
}
#pragma mark - Internal, for testing
+ (void)clearLibraryFiles
{
[[NSFileManager defaultManager] removeItemAtPath:[[self class] persistenceFilePath:FBSDK_APPEVENTSUTILITY_ANONYMOUSIDFILENAME]
error:NULL];
[[NSFileManager defaultManager] removeItemAtPath:[[self class] persistenceFilePath:FBSDKTimeSpentFilename]
error:NULL];
}
+ (void)ensureOnMainThread:(NSString *)methodName className:(NSString *)className
{
FBSDKConditionalLog([NSThread isMainThread],
FBSDKLoggingBehaviorDeveloperErrors,
@"*** <%@, %@> is not called on the main thread. This can lead to errors.",
methodName,
className);
}
+ (NSString *)flushReasonToString:(FBSDKAppEventsFlushReason)flushReason
{
NSString *result = @"Unknown";
switch (flushReason) {
case FBSDKAppEventsFlushReasonExplicit:
result = @"Explicit";
break;
case FBSDKAppEventsFlushReasonTimer:
result = @"Timer";
break;
case FBSDKAppEventsFlushReasonSessionChange:
result = @"SessionChange";
break;
case FBSDKAppEventsFlushReasonPersistedEvents:
result = @"PersistedEvents";
break;
case FBSDKAppEventsFlushReasonEventThreshold:
result = @"EventCountThreshold";
break;
case FBSDKAppEventsFlushReasonEagerlyFlushingEvent:
result = @"EagerlyFlushingEvent";
break;
}
return result;
}
+ (void)logAndNotify:(NSString *)msg
{
[[self class] logAndNotify:msg allowLogAsDeveloperError:YES];
}
+ (void)logAndNotify:(NSString *)msg allowLogAsDeveloperError:(BOOL)allowLogAsDeveloperError
{
NSString *behaviorToLog = FBSDKLoggingBehaviorAppEvents;
if (allowLogAsDeveloperError) {
if ([FBSDKSettings.loggingBehaviors containsObject:FBSDKLoggingBehaviorDeveloperErrors]) {
// Rather than log twice, prefer 'DeveloperErrors' if it's set over AppEvents.
behaviorToLog = FBSDKLoggingBehaviorDeveloperErrors;
}
}
[FBSDKLogger singleShotLogEntry:behaviorToLog logEntry:msg];
NSError *error = [FBSDKError errorWithCode:FBSDKErrorAppEventsFlush message:msg];
[[NSNotificationCenter defaultCenter] postNotificationName:FBSDKAppEventsLoggingResultNotification object:error];
}
+ (BOOL)matchString:(NSString *)string
firstCharacterSet:(NSCharacterSet *)firstCharacterSet
restOfStringCharacterSet:(NSCharacterSet *)restOfStringCharacterSet
{
if (string.length == 0) {
return NO;
}
for (NSUInteger i = 0; i < string.length; i++) {
const unichar c = [string characterAtIndex:i];
if (i == 0) {
if (![firstCharacterSet characterIsMember:c]) {
return NO;
}
} else {
if (![restOfStringCharacterSet characterIsMember:c]) {
return NO;
}
}
}
return YES;
}
+ (BOOL)regexValidateIdentifier:(NSString *)identifier
{
static NSCharacterSet *firstCharacterSet;
static NSCharacterSet *restOfStringCharacterSet;
static dispatch_once_t onceToken;
static NSMutableSet *cachedIdentifiers;
dispatch_once(&onceToken, ^{
NSMutableCharacterSet *mutableSet = [NSMutableCharacterSet alphanumericCharacterSet];
[mutableSet addCharactersInString:@"_"];
firstCharacterSet = [mutableSet copy];
[mutableSet addCharactersInString:@"- "];
restOfStringCharacterSet = [mutableSet copy];
cachedIdentifiers = [[NSMutableSet alloc] init];
});
@synchronized(self) {
if (![cachedIdentifiers containsObject:identifier]) {
if ([self matchString:identifier
firstCharacterSet:firstCharacterSet
restOfStringCharacterSet:restOfStringCharacterSet]) {
[cachedIdentifiers addObject:identifier];
} else {
return NO;
}
}
}
return YES;
}
+ (BOOL)validateIdentifier:(NSString *)identifier
{
if (identifier == nil || identifier.length == 0 || identifier.length > FBSDK_APPEVENTSUTILITY_MAX_IDENTIFIER_LENGTH || ![[self class] regexValidateIdentifier:identifier]) {
[[self class] logAndNotify:[NSString stringWithFormat:@"Invalid identifier: '%@'. Must be between 1 and %d characters, and must be contain only alphanumerics, _, - or spaces, starting with alphanumeric or _.",
identifier, FBSDK_APPEVENTSUTILITY_MAX_IDENTIFIER_LENGTH]];
return NO;
}
return YES;
}
// Given a candidate token (which may be nil), find the real token to string to use.
// Precedence: 1) provided token, 2) current token, 3) app | client token, 4) fully anonymous session.
+ (NSString *)tokenStringToUseFor:(FBSDKAccessToken *)token
{
if (!token) {
token = [FBSDKAccessToken currentAccessToken];
}
NSString *appID = [FBSDKAppEvents loggingOverrideAppID] ?: token.appID ?: [FBSDKSettings appID];
NSString *tokenString = token.tokenString;
if (!tokenString || ![appID isEqualToString:token.appID]) {
// If there's an logging override app id present, then we don't want to use the client token since the client token
// is intended to match up with the primary app id (and AppEvents doesn't require a client token).
NSString *clientTokenString = [FBSDKSettings clientToken];
if (clientTokenString && appID && [appID isEqualToString:token.appID]){
tokenString = [NSString stringWithFormat:@"%@|%@", appID, clientTokenString];
} else if (appID) {
tokenString = nil;
}
}
return tokenString;
}
+ (long)unixTimeNow
{
return (long)round([NSDate date].timeIntervalSince1970);
}
+ (id)getVariable:(NSString *)variableName fromInstance:(NSObject *)instance {
Ivar ivar = class_getInstanceVariable([instance class], variableName.UTF8String);
if (ivar != NULL) {
const char *encoding = ivar_getTypeEncoding(ivar);
if (encoding != NULL && encoding[0] == '@') {
return object_getIvar(instance, ivar);
}
}
return nil;
}
+ (NSNumber *)getNumberValue:(NSString *)text {
NSNumber *value = @0;
NSLocale *locale = [NSLocale currentLocale];
NSString *ds = [locale objectForKey:NSLocaleDecimalSeparator] ?: @".";
NSString *gs = [locale objectForKey:NSLocaleGroupingSeparator] ?: @",";
NSString *separators = [ds stringByAppendingString:gs];
NSString *regex = [NSString stringWithFormat:@"[+-]?([0-9]+[%1$@]?)?[%1$@]?([0-9]+[%1$@]?)+", separators];
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:regex
options:0
error:nil];
NSTextCheckingResult *match = [re firstMatchInString:text
options:0
range:NSMakeRange(0, text.length)];
if (match) {
NSString *validText = [text substringWithRange:match.range];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.locale = locale;
formatter.numberStyle = NSNumberFormatterDecimalStyle;
value = [formatter numberFromString:validText];
if (nil == value) {
value = @(validText.floatValue);
}
}
return value;
}
+ (BOOL)isDebugBuild {
#if TARGET_IPHONE_SIMULATOR
return YES;
#else
BOOL isDevelopment = NO;
// There is no provisioning profile in AppStore Apps.
@try
{
NSData *data = [NSData dataWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"embedded" ofType:@"mobileprovision"]];
if (data) {
const char *bytes = [data bytes];
NSMutableString *profile = [[NSMutableString alloc] initWithCapacity:data.length];
for (NSUInteger i = 0; i < data.length; i++) {
[profile appendFormat:@"%c", bytes[i]];
}
// Look for debug value, if detected we're in a development build.
NSString *cleared = [[profile componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet] componentsJoinedByString:@""];
isDevelopment = ([cleared rangeOfString:@"<key>get-task-allow</key><true/>"].length > 0);
}
return isDevelopment;
}
@catch(NSException *exception)
{
}
return NO;
#endif
}
+ (BOOL)isSensitiveUserData:(NSString *)text
{
if (0 == text.length) {
return NO;
}
return [self isEmailAddress:text] || [self isCreditCardNumber:text];
}
+ (BOOL)isCreditCardNumber:(NSString *)text
{
text = [[text componentsSeparatedByCharactersInSet:[NSCharacterSet.decimalDigitCharacterSet invertedSet]] componentsJoinedByString:@""];
if (text.doubleValue == 0) {
return NO;
}
if (text.length < 9 || text.length > 21) {
return NO;
}
const char *chars = [text cStringUsingEncoding:NSUTF8StringEncoding];
if (NULL == chars) {
return NO;
}
BOOL isOdd = YES;
int oddSum = 0;
int evenSum = 0;
for (int i = (int)text.length - 1; i >= 0; i--) {
int digit = chars[i] - '0';
if (isOdd)
oddSum += digit;
else
evenSum += digit / 5 + (2 * digit) % 10;
isOdd = !isOdd;
}
return ((oddSum + evenSum) % 10 == 0);
}
+ (BOOL)isEmailAddress:(NSString *)text
{
NSString *pattern = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSUInteger matches = [regex numberOfMatchesInString:text options:0 range:NSMakeRange(0, [text length])];
return matches > 0;
}
@end

View File

@ -0,0 +1,31 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
NS_SWIFT_NAME(HybridAppEventsScriptMessageHandler)
@interface FBSDKHybridAppEventsScriptMessageHandler : NSObject <WKScriptMessageHandler>
@end
#endif

View File

@ -0,0 +1,74 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKHybridAppEventsScriptMessageHandler.h"
#if SWIFT_PACKAGE
#import "FBSDKAppEvents.h"
#else
#import <FBSDKCoreKit/FBSDKAppEvents.h>
#endif
#import "FBSDKAppEvents+Internal.h"
NSString *const FBSDKAppEventsWKWebViewMessagesPixelReferralParamKey = @"_fb_pixel_referral_id";
@class WKUserContentController;
@implementation FBSDKHybridAppEventsScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:FBSDKAppEventsWKWebViewMessagesHandlerKey]) {
NSString *event = message.body[FBSDKAppEventsWKWebViewMessagesEventKey];
if (event.length > 0) {
NSString *stringedParams = message.body[FBSDKAppEventsWKWebViewMessagesParamsKey];
NSMutableDictionary <NSString *, id> *params = nil;
NSError *jsonParseError = nil;
if ([stringedParams isKindOfClass:[NSString class]]) {
params = [NSJSONSerialization JSONObjectWithData:[stringedParams dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingMutableContainers
error:&jsonParseError
];
}
NSString *pixelID = message.body[FBSDKAppEventsWKWebViewMessagesPixelIDKey];
if (pixelID == nil) {
[FBSDKAppEventsUtility logAndNotify:@"Can't bridge an event without a referral Pixel ID. Check your webview Pixel configuration."];
return;
}
if (jsonParseError != nil || ![params isKindOfClass:[NSDictionary class]] || params == nil) {
[FBSDKAppEventsUtility logAndNotify:@"Could not find parameters for your Pixel request. Check your webview Pixel configuration."];
params = [[NSMutableDictionary alloc] initWithObjectsAndKeys:pixelID, FBSDKAppEventsWKWebViewMessagesPixelReferralParamKey, nil];
}
else {
params[FBSDKAppEventsWKWebViewMessagesPixelReferralParamKey] = pixelID;
}
[FBSDKAppEvents logInternalEvent:event
parameters:params
isImplicitlyLogged:NO];
}
}
}
@end
#endif

View File

@ -0,0 +1,26 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
// Class to encapsulate implicit logging of purchase events
NS_SWIFT_NAME(PaymentObserver)
@interface FBSDKPaymentObserver : NSObject
+ (void)startObservingTransactions;
+ (void)stopObservingTransactions;
@end

View File

@ -0,0 +1,547 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKPaymentObserver.h"
#import <StoreKit/StoreKit.h>
#import "FBSDKCoreKit+Internal.h"
#import "FBSDKAppEvents+Internal.h"
#import "FBSDKDynamicFrameworkLoader.h"
#import "FBSDKLogger.h"
#import "FBSDKSettings.h"
static NSString *const FBSDKPaymentObserverOriginalTransactionKey = @"com.facebook.appevents.PaymentObserver.originalTransaction";
static NSString *const FBSDKPaymentObserverDelimiter = @",";
static NSString *const FBSDKAppEventParameterImplicitlyLoggedPurchase = @"_implicitlyLogged";
static NSString *const FBSDKAppEventNamePurchaseFailed = @"fb_mobile_purchase_failed";
static NSString *const FBSDKAppEventNamePurchaseRestored = @"fb_mobile_purchase_restored";
static NSString *const FBSDKAppEventParameterNameInAppPurchaseType = @"fb_iap_product_type";
static NSString *const FBSDKAppEventParameterNameProductTitle = @"fb_content_title";
static NSString *const FBSDKAppEventParameterNameOriginalTransactionID = @"fb_original_transaction_id";
static NSString *const FBSDKAppEventParameterNameTransactionID = @"fb_transaction_id";
static NSString *const FBSDKAppEventParameterNameTransactionDate = @"fb_transaction_date";
static NSString *const FBSDKAppEventParameterNameSubscriptionPeriod = @"fb_iap_subs_period";
static NSString *const FBSDKAppEventParameterNameIsStartTrial = @"fb_iap_is_start_trial";
static NSString *const FBSDKAppEventParameterNameHasFreeTrial = @"fb_iap_has_free_trial";
static NSString *const FBSDKAppEventParameterNameTrialPeriod = @"fb_iap_trial_period";
static NSString *const FBSDKAppEventParameterNameTrialPrice = @"fb_iap_trial_price";
static int const FBSDKMaxParameterValueLength = 100;
static NSMutableArray *g_pendingRequestors;
static NSString *const FBSDKGateKeeperAppEventsIfAutoLogSubs = @"app_events_if_auto_log_subs";
@interface FBSDKPaymentProductRequestor : NSObject<SKProductsRequestDelegate>
@property (nonatomic, retain) SKPaymentTransaction *transaction;
- (instancetype)initWithTransaction:(SKPaymentTransaction*)transaction;
- (void)resolveProducts;
@end
@interface FBSDKPaymentObserver() <SKPaymentTransactionObserver>
@end
@implementation FBSDKPaymentObserver
{
BOOL _observingTransactions;
}
+ (void)startObservingTransactions
{
[[self singleton] startObservingTransactions];
}
+ (void)stopObservingTransactions
{
[[self singleton] stopObservingTransactions];
}
#pragma mark - Internal Methods
+ (FBSDKPaymentObserver *)singleton
{
static dispatch_once_t pred;
static FBSDKPaymentObserver *shared = nil;
dispatch_once(&pred, ^{
shared = [[FBSDKPaymentObserver alloc] init];
});
return shared;
}
- (instancetype) init
{
self = [super init];
if (self) {
_observingTransactions = NO;
}
return self;
}
- (void)startObservingTransactions
{
@synchronized (self) {
if (!_observingTransactions) {
[(SKPaymentQueue *)[fbsdkdfl_SKPaymentQueueClass() defaultQueue] addTransactionObserver:self];
_observingTransactions = YES;
}
}
}
- (void)stopObservingTransactions
{
@synchronized (self) {
if (_observingTransactions) {
[(SKPaymentQueue *)[fbsdkdfl_SKPaymentQueueClass() defaultQueue] removeTransactionObserver:self];
_observingTransactions = NO;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
case SKPaymentTransactionStatePurchased:
case SKPaymentTransactionStateFailed:
case SKPaymentTransactionStateRestored:
[self handleTransaction:transaction];
break;
case SKPaymentTransactionStateDeferred:
break;
}
}
}
- (void)handleTransaction:(SKPaymentTransaction *)transaction
{
FBSDKPaymentProductRequestor *productRequest = [[FBSDKPaymentProductRequestor alloc] initWithTransaction:transaction];
[productRequest resolveProducts];
}
@end
@interface FBSDKPaymentProductRequestor()
@property (nonatomic, retain) SKProductsRequest *productRequest;
@end
@implementation FBSDKPaymentProductRequestor
{
NSMutableSet<NSString *> *_originalTransactionSet;
NSSet<NSString *> *_eventsWithReceipt;
NSDateFormatter *_formatter;
}
+ (void)initialize
{
if ([self class] == [FBSDKPaymentProductRequestor class]) {
g_pendingRequestors = [[NSMutableArray alloc] init];
}
}
- (instancetype)initWithTransaction:(SKPaymentTransaction*)transaction
{
self = [super init];
if (self) {
_transaction = transaction;
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"yyyy-MM-dd HH:mm:ssZ";
NSString *data = [[NSUserDefaults standardUserDefaults] stringForKey:FBSDKPaymentObserverOriginalTransactionKey];
_eventsWithReceipt = [NSSet setWithArray:@[FBSDKAppEventNamePurchased, FBSDKAppEventNameSubscribe,
FBSDKAppEventNameStartTrial]];
if (data) {
_originalTransactionSet = [NSMutableSet setWithArray:[data componentsSeparatedByString:FBSDKPaymentObserverDelimiter]];
} else {
_originalTransactionSet = [[NSMutableSet alloc] init];
}
}
return self;
}
- (void)setProductRequest:(SKProductsRequest *)productRequest
{
if (productRequest != _productRequest) {
if (_productRequest) {
_productRequest.delegate = nil;
}
_productRequest = productRequest;
}
}
- (void)resolveProducts
{
NSString *productId = self.transaction.payment.productIdentifier;
NSSet *productIdentifiers = [NSSet setWithObjects:productId, nil];
self.productRequest = [[fbsdkdfl_SKProductsRequestClass() alloc] initWithProductIdentifiers:productIdentifiers];
self.productRequest.delegate = self;
@synchronized(g_pendingRequestors) {
[g_pendingRequestors addObject:self];
}
[self.productRequest start];
}
- (NSString *)getTruncatedString:(NSString *)inputString
{
if (!inputString) {
return @"";
}
return inputString.length <= FBSDKMaxParameterValueLength ? inputString : [inputString substringToIndex:FBSDKMaxParameterValueLength];
}
- (void)logTransactionEvent:(SKProduct *)product
{
if ([self isSubscription:product] &&
[FBSDKGateKeeperManager boolForKey:FBSDKGateKeeperAppEventsIfAutoLogSubs
defaultValue:NO]) {
[self logImplicitSubscribeTransaction:self.transaction ofProduct:product];
} else {
[self logImplicitPurchaseTransaction:self.transaction ofProduct:product];
}
}
- (BOOL)isSubscription:(SKProduct *)product
{
#if !TARGET_OS_TV
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_1
if (@available(iOS 11.2, *)) {
return (product.subscriptionPeriod != nil) && ((unsigned long)product.subscriptionPeriod.numberOfUnits > 0);
}
#endif
#endif
return NO;
}
- (NSMutableDictionary<NSString *, id> *)getEventParametersOfProduct:(SKProduct *)product
withTransaction:(SKPaymentTransaction *)transaction
{
NSString *transactionID = nil;
NSString *transactionDate = nil;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStatePurchased:
transactionID = self.transaction.transactionIdentifier;
transactionDate = [_formatter stringFromDate:self.transaction.transactionDate];
break;
case SKPaymentTransactionStateFailed:
break;
case SKPaymentTransactionStateRestored:
transactionDate = [_formatter stringFromDate:self.transaction.transactionDate];
break;
default: break;
}
SKPayment *payment = transaction.payment;
NSMutableDictionary *eventParameters = [NSMutableDictionary dictionaryWithDictionary: @{
FBSDKAppEventParameterNameContentID: payment.productIdentifier ?: @"",
FBSDKAppEventParameterNameNumItems: @(payment.quantity),
FBSDKAppEventParameterNameTransactionDate: transactionDate ?: @"",
}];
if (product) {
[eventParameters addEntriesFromDictionary: @{
FBSDKAppEventParameterNameCurrency: [product.priceLocale objectForKey:NSLocaleCurrencyCode],
FBSDKAppEventParameterNameNumItems: @(payment.quantity),
FBSDKAppEventParameterNameProductTitle: [self getTruncatedString:product.localizedTitle],
FBSDKAppEventParameterNameDescription: [self getTruncatedString:product.localizedDescription],
}];
if (transactionID) {
eventParameters[FBSDKAppEventParameterNameTransactionID] = transactionID;
}
}
#if !TARGET_OS_TV
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_1
if (@available(iOS 11.2, *)) {
if ([self isSubscription:product]) {
// subs inapp
eventParameters[FBSDKAppEventParameterNameSubscriptionPeriod] = [self durationOfSubscriptionPeriod:product.subscriptionPeriod];
eventParameters[FBSDKAppEventParameterNameInAppPurchaseType] = @"subs";
eventParameters[FBSDKAppEventParameterNameIsStartTrial] = [self isStartTrial:transaction ofProduct:product] ? @"1" : @"0";
// trial information for subs
SKProductDiscount *discount = product.introductoryPrice;
if (discount) {
if (discount.paymentMode == SKProductDiscountPaymentModeFreeTrial) {
eventParameters[FBSDKAppEventParameterNameHasFreeTrial] = @"1";
} else {
eventParameters[FBSDKAppEventParameterNameHasFreeTrial] = @"0";
}
eventParameters[FBSDKAppEventParameterNameTrialPeriod] = [self durationOfSubscriptionPeriod:discount.subscriptionPeriod];
eventParameters[FBSDKAppEventParameterNameTrialPrice] = discount.price;
}
} else {
eventParameters[FBSDKAppEventParameterNameInAppPurchaseType] = @"inapp";
}
}
#endif
#endif
return eventParameters;
}
- (void)appendOriginalTransactionID:(NSString *)transactionID
{
if (!transactionID) {
return;
}
[_originalTransactionSet addObject:transactionID];
[[NSUserDefaults standardUserDefaults] setObject:[[_originalTransactionSet allObjects] componentsJoinedByString:FBSDKPaymentObserverDelimiter]
forKey:FBSDKPaymentObserverOriginalTransactionKey];
}
- (void)clearOriginalTransactionID:(NSString *)transactionID
{
if (!transactionID) {
return;
}
[_originalTransactionSet removeObject:transactionID];
[[NSUserDefaults standardUserDefaults] setObject:[[_originalTransactionSet allObjects] componentsJoinedByString:FBSDKPaymentObserverDelimiter]
forKey:FBSDKPaymentObserverOriginalTransactionKey];
}
- (BOOL)isStartTrial:(SKPaymentTransaction *)transaction
ofProduct:(SKProduct *)product
{
#if !TARGET_OS_TV
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_1
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_4
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_12_1
// promotional offer starting from iOS 12.2
if (@available(iOS 12.2, *)) {
SKPaymentDiscount *paymentDiscount = transaction.payment.paymentDiscount;
if (paymentDiscount) {
NSArray<SKProductDiscount *> *discounts = product.discounts;
for (SKProductDiscount *discount in discounts) {
if (discount.paymentMode == SKProductDiscountPaymentModeFreeTrial &&
[paymentDiscount.identifier isEqualToString:discount.identifier]) {
return YES;
}
}
}
}
#endif
#endif
// introductory offer starting from iOS 11.2
if (@available(iOS 11.2, *)) {
if (product.introductoryPrice &&
product.introductoryPrice.paymentMode == SKProductDiscountPaymentModeFreeTrial) {
NSString *originalTransactionID = transaction.originalTransaction.transactionIdentifier;
// only consider the very first trial transaction as start trial
if (!originalTransactionID) {
return YES;
}
}
}
#endif
#endif
return NO;
}
- (BOOL)hasStartTrial:(SKProduct *)product
{
#if !TARGET_OS_TV
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_1
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_4
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_12_1
// promotional offer starting from iOS 12.2
if (@available(iOS 12.2, *)) {
NSArray<SKProductDiscount *> *discounts = product.discounts;
for (SKProductDiscount *discount in discounts) {
if (discount.paymentMode == SKProductDiscountPaymentModeFreeTrial) {
return YES;
}
}
}
#endif
#endif
// introductory offer starting from iOS 11.2
if (@available(iOS 11.2, *)) {
if (product.introductoryPrice && (product.introductoryPrice.paymentMode == SKProductDiscountPaymentModeFreeTrial)) {
return YES;
}
}
#endif
#endif
return NO;
}
- (NSString *)durationOfSubscriptionPeriod:(id)subcriptionPeriod
{
#if !TARGET_OS_TV
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_11_1
if (@available(iOS 11.2, *)) {
if (subcriptionPeriod && [subcriptionPeriod isKindOfClass:[SKProductSubscriptionPeriod class]]) {
SKProductSubscriptionPeriod *period = (SKProductSubscriptionPeriod *)subcriptionPeriod;
NSString *unit = nil;
switch (period.unit) {
case SKProductPeriodUnitDay: unit = @"D"; break;
case SKProductPeriodUnitWeek: unit = @"W"; break;
case SKProductPeriodUnitMonth: unit = @"M"; break;
case SKProductPeriodUnitYear: unit = @"Y"; break;
}
return [NSString stringWithFormat:@"P%lu%@", (unsigned long)period.numberOfUnits, unit];
}
}
#endif
#endif
return nil;
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray* products = response.products;
NSArray* invalidProductIdentifiers = response.invalidProductIdentifiers;
if (products.count + invalidProductIdentifiers.count != 1) {
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
formatString:@"FBSDKPaymentObserver: Expect to resolve one product per request"];
}
SKProduct *product = nil;
if (products.count) {
product = products[0];
}
[self logTransactionEvent:product];
}
- (void)requestDidFinish:(SKRequest *)request
{
[self cleanUp];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
[self logTransactionEvent:nil];
[self cleanUp];
}
- (void)cleanUp
{
@synchronized(g_pendingRequestors) {
[g_pendingRequestors removeObject:self];
}
}
- (void)logImplicitSubscribeTransaction:(SKPaymentTransaction *)transaction
ofProduct:(SKProduct *)product
{
NSString *eventName = nil;
NSString *originalTransactionID = transaction.originalTransaction.transactionIdentifier;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
eventName = @"SubscriptionInitiatedCheckout";
break;
case SKPaymentTransactionStatePurchased:
if ([self isStartTrial:transaction ofProduct:product]) {
eventName = FBSDKAppEventNameStartTrial;
[self clearOriginalTransactionID:originalTransactionID];
} else {
if (originalTransactionID && [_originalTransactionSet containsObject:originalTransactionID]) {
return;
}
eventName = FBSDKAppEventNameSubscribe;
[self appendOriginalTransactionID:(originalTransactionID ?: transaction.transactionIdentifier)];
}
break;
case SKPaymentTransactionStateFailed:
eventName = @"SubscriptionFailed";
break;
case SKPaymentTransactionStateRestored:
eventName = @"SubscriptionRestore";
break;
case SKPaymentTransactionStateDeferred:
return;
}
double totalAmount = 0;
if (product) {
totalAmount = transaction.payment.quantity * product.price.doubleValue;
}
[self logImplicitTransactionEvent:eventName
valueToSum:totalAmount
parameters:[self getEventParametersOfProduct:product withTransaction:transaction]];
}
- (void)logImplicitPurchaseTransaction:(SKPaymentTransaction *)transaction
ofProduct:(SKProduct *)product
{
NSString *eventName = nil;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
eventName = FBSDKAppEventNameInitiatedCheckout;
break;
case SKPaymentTransactionStatePurchased:
eventName = FBSDKAppEventNamePurchased;
break;
case SKPaymentTransactionStateFailed:
eventName = FBSDKAppEventNamePurchaseFailed;
break;
case SKPaymentTransactionStateRestored:
eventName = FBSDKAppEventNamePurchaseRestored;
break;
case SKPaymentTransactionStateDeferred:
return;
}
double totalAmount = 0;
if (product) {
totalAmount = transaction.payment.quantity * product.price.doubleValue;
}
[self logImplicitTransactionEvent:eventName
valueToSum:totalAmount
parameters:[self getEventParametersOfProduct:product withTransaction:transaction]];
}
- (void)logImplicitTransactionEvent:(NSString *)eventName
valueToSum:(double)valueToSum
parameters:(NSDictionary<NSString *, id> *)parameters
{
NSMutableDictionary *eventParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
if ([_eventsWithReceipt containsObject:eventName]) {
NSData* receipt = [self fetchDeviceReceipt];
if (receipt) {
NSString *base64encodedReceipt = [receipt base64EncodedStringWithOptions:0];
eventParameters[@"receipt_data"] = base64encodedReceipt;
}
}
eventParameters[FBSDKAppEventParameterImplicitlyLoggedPurchase] = @"1";
[FBSDKAppEvents logEvent:eventName
valueToSum:valueToSum
parameters:eventParameters];
// Unless the behavior is set to only allow explicit flushing, we go ahead and flush, since purchase events
// are relatively rare and relatively high value and worth getting across on wire right away.
if ([FBSDKAppEvents flushBehavior] != FBSDKAppEventsFlushBehaviorExplicitOnly) {
[[FBSDKAppEvents singleton] flushForReason:FBSDKAppEventsFlushReasonEagerlyFlushingEvent];
}
}
// Fetch the current receipt for this application.
- (NSData*)fetchDeviceReceipt
{
NSURL *receiptURL = [NSBundle bundleForClass:[self class]].appStoreReceiptURL;
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
return receipt;
}
@end

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT NSString *const FBSDKTimeSpentFilename;
// Class to encapsulate persisting of time spent data collected by [FBSDKAppEvents activateApp]. The activate app App Event is
// logged when restore: is called with sufficient time since the last deactivation.
NS_SWIFT_NAME(TimeSpentData)
@interface FBSDKTimeSpentData : NSObject
+ (void)suspend;
+ (void)restore:(BOOL)calledFromActivateApp;
+ (void)setSourceApplication:(NSString *)sourceApplication openURL:(NSURL *)url;
+ (void)setSourceApplication:(NSString *)sourceApplication isFromAppLink:(BOOL)isFromAppLink;
+ (void)registerAutoResetSourceApplication;
@end

View File

@ -0,0 +1,319 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKTimeSpentData.h"
#import "FBSDKAppEvents+Internal.h"
#import "FBSDKAppEventsUtility.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKLogger.h"
#import "FBSDKServerConfiguration.h"
#import "FBSDKServerConfigurationManager.h"
#import "FBSDKSettings.h"
// Filename and keys for session length
NSString *const FBSDKTimeSpentFilename = @"com-facebook-sdk-AppEventsTimeSpent.json";
static NSString *const FBSDKTimeSpentPersistKeySessionSecondsSpent = @"secondsSpentInCurrentSession";
static NSString *const FBSDKTimeSpentPersistKeySessionNumInterruptions = @"numInterruptions";
static NSString *const FBSDKTimeSpentPersistKeyLastSuspendTime = @"lastSuspendTime";
static NSString *const FBSDKTimeSpentPersistKeySessionID = @"sessionID";
static NSString *const FBSDKAppEventNameActivatedApp = @"fb_mobile_activate_app";
static NSString *const FBSDKAppEventNameDeactivatedApp = @"fb_mobile_deactivate_app";
static NSString *const FBSDKAppEventParameterNameSessionInterruptions = @"fb_mobile_app_interruptions";
static NSString *const FBSDKAppEventParameterNameTimeBetweenSessions = @"fb_mobile_time_between_sessions";
static NSString *const FBSDKAppEventParameterNameSessionID = @"_session_id";
static const int SECS_PER_MIN = 60;
static const int SECS_PER_HOUR = 60 * SECS_PER_MIN;
static const int SECS_PER_DAY = 24 * SECS_PER_HOUR;
static NSString *g_sourceApplication;
static BOOL g_isOpenedFromAppLink;
// Will be translated and displayed in App Insights. Need to maintain same number and value of quanta on the server.
static const long INACTIVE_SECONDS_QUANTA[] =
{
5 * SECS_PER_MIN,
15 * SECS_PER_MIN,
30 * SECS_PER_MIN,
1 * SECS_PER_HOUR,
6 * SECS_PER_HOUR,
12 * SECS_PER_HOUR,
1 * SECS_PER_DAY,
2 * SECS_PER_DAY,
3 * SECS_PER_DAY,
7 * SECS_PER_DAY,
14 * SECS_PER_DAY,
21 * SECS_PER_DAY,
28 * SECS_PER_DAY,
60 * SECS_PER_DAY,
90 * SECS_PER_DAY,
120 * SECS_PER_DAY,
150 * SECS_PER_DAY,
180 * SECS_PER_DAY,
365 * SECS_PER_DAY,
LONG_MAX, // keep as LONG_MAX to guarantee loop will terminate
};
/**
* This class encapsulates the notion of an app 'session' - the length of time that the user has
* spent in the app that can be considered a single usage of the app. Apps may be frequently interrupted
* do to other device activity, like a text message, so this class allows those interruptions to be smoothed
* out and the time actually spent in the app excluding this interruption time to be accumulated. Also,
* once a certain amount of time has gone by where the app is not in the foreground, we consider the
* session to be complete, and a new session beginning. When this occurs, we log a 'deactivate app' event
* with the duration of the previous session as the 'value' of this event, along with the number of
* interruptions from that previous session as an event parameter.
*/
@implementation FBSDKTimeSpentData
{
BOOL _isCurrentlyLoaded;
BOOL _shouldLogActivateEvent;
BOOL _shouldLogDeactivateEvent;
long _secondsSpentInCurrentSession;
long _timeSinceLastSuspend;
int _numInterruptionsInCurrentSession;
long _lastRestoreTime;
long _lastSuspendTime;
NSString *_sessionID;
}
//
// Public methods
//
+ (void)suspend
{
[self.singleton instanceSuspend];
}
+ (void)restore:(BOOL)calledFromActivateApp
{
[self.singleton instanceRestore:calledFromActivateApp];
}
//
// Internal methods
//
+ (FBSDKTimeSpentData *)singleton
{
static dispatch_once_t pred;
static FBSDKTimeSpentData *shared = nil;
dispatch_once(&pred, ^{
shared = [[FBSDKTimeSpentData alloc] init];
});
return shared;
}
// Calculate and persist time spent data for this instance of the app activation.
- (void)instanceSuspend
{
[FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];
if (!_isCurrentlyLoaded) {
FBSDKConditionalLog(YES, FBSDKLoggingBehaviorInformational, @"[FBSDKTimeSpentData suspend] invoked without corresponding restore");
return;
}
long now = [FBSDKAppEventsUtility unixTimeNow];
long timeSinceRestore = now - _lastRestoreTime;
// Can happen if the clock on the device is changed
if (timeSinceRestore < 0) {
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
formatString:@"Clock skew detected"];
timeSinceRestore = 0;
}
_secondsSpentInCurrentSession += timeSinceRestore;
NSDictionary *timeSpentData =
@{
FBSDKTimeSpentPersistKeySessionSecondsSpent : @(_secondsSpentInCurrentSession),
FBSDKTimeSpentPersistKeySessionNumInterruptions : @(_numInterruptionsInCurrentSession),
FBSDKTimeSpentPersistKeyLastSuspendTime : @(now),
FBSDKTimeSpentPersistKeySessionID : _sessionID,
};
NSString *content = [FBSDKBasicUtility JSONStringForObject:timeSpentData error:NULL invalidObjectHandler:NULL];
[content writeToFile:[FBSDKBasicUtility persistenceFilePath:FBSDKTimeSpentFilename]
atomically:YES
encoding:NSASCIIStringEncoding
error:nil];
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
formatString:@"FBSDKTimeSpentData Persist: %@", content];
_isCurrentlyLoaded = NO;
}
// Called during activation - either through an explicit 'activateApp' call or implicitly when the app is foregrounded.
// In both cases, we restore the persisted event data. In the case of the activateApp, we log an 'app activated'
// event if there's been enough time between the last deactivation and now.
- (void)instanceRestore:(BOOL)calledFromActivateApp
{
[FBSDKAppEventsUtility ensureOnMainThread:NSStringFromSelector(_cmd) className:NSStringFromClass([self class])];
// It's possible to call this multiple times during the time the app is in the foreground. If this is the case,
// just restore persisted data the first time.
if (!_isCurrentlyLoaded) {
NSString *content =
[[NSString alloc] initWithContentsOfFile:[FBSDKBasicUtility persistenceFilePath:FBSDKTimeSpentFilename]
usedEncoding:nil
error:nil];
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorAppEvents
formatString:@"FBSDKTimeSpentData Restore: %@", content];
long now = [FBSDKAppEventsUtility unixTimeNow];
if (!content) {
// Nothing persisted, so this is the first launch.
_sessionID = [NSUUID UUID].UUIDString;
_secondsSpentInCurrentSession = 0;
_numInterruptionsInCurrentSession = 0;
_lastSuspendTime = 0;
// We want to log the app activation event on the first launch, but not the deactivate event
_shouldLogActivateEvent = YES;
_shouldLogDeactivateEvent = NO;
} else {
NSDictionary<id, id> *results = [FBSDKBasicUtility objectForJSONString:content error:NULL];
_lastSuspendTime = [results[FBSDKTimeSpentPersistKeyLastSuspendTime] longValue];
_timeSinceLastSuspend = now - _lastSuspendTime;
_secondsSpentInCurrentSession = [results[FBSDKTimeSpentPersistKeySessionSecondsSpent] intValue];
_sessionID = results[FBSDKTimeSpentPersistKeySessionID] ? : [NSUUID UUID].UUIDString;
_numInterruptionsInCurrentSession = [results[FBSDKTimeSpentPersistKeySessionNumInterruptions] intValue];
_shouldLogActivateEvent = (_timeSinceLastSuspend > [FBSDKServerConfigurationManager cachedServerConfiguration].sessionTimoutInterval);
// Other than the first launch, we always log the last session's deactivate with this session's activate.
_shouldLogDeactivateEvent = _shouldLogActivateEvent;
if (!_shouldLogDeactivateEvent) {
// If we're not logging, then the time we spent deactivated is considered another interruption. But cap it
// so errant or test uses doesn't blow out the cardinality on the backend processing
_numInterruptionsInCurrentSession = MIN(_numInterruptionsInCurrentSession + 1, 200);
}
}
_lastRestoreTime = now;
_isCurrentlyLoaded = YES;
if (calledFromActivateApp) {
// It's important to log deactivate first to reset sessionID
if (_shouldLogDeactivateEvent) {
[FBSDKAppEvents logEvent:FBSDKAppEventNameDeactivatedApp
valueToSum:_secondsSpentInCurrentSession
parameters:[self appEventsParametersForDeactivate]];
// We've logged the session stats, now reset.
_secondsSpentInCurrentSession = 0;
_numInterruptionsInCurrentSession = 0;
_sessionID = [NSUUID UUID].UUIDString;
}
if (_shouldLogActivateEvent) {
[FBSDKAppEvents logEvent:FBSDKAppEventNameActivatedApp
parameters:[self appEventsParametersForActivate]];
// Unless the behavior is set to only allow explicit flushing, we go ahead and flush. App launch
// events are critical to Analytics so we don't want to lose them.
if ([FBSDKAppEvents flushBehavior] != FBSDKAppEventsFlushBehaviorExplicitOnly) {
[[FBSDKAppEvents singleton] flushForReason:FBSDKAppEventsFlushReasonEagerlyFlushingEvent];
}
}
}
}
}
- (NSDictionary *)appEventsParametersForActivate
{
return @{
FBSDKAppEventParameterLaunchSource: [[self class] getSourceApplication],
FBSDKAppEventParameterNameSessionID: _sessionID,
};
}
- (NSDictionary *)appEventsParametersForDeactivate
{
int quantaIndex = 0;
while (_timeSinceLastSuspend > INACTIVE_SECONDS_QUANTA[quantaIndex]) {
quantaIndex++;
}
NSMutableDictionary *params = [@{ FBSDKAppEventParameterNameSessionInterruptions : @(_numInterruptionsInCurrentSession),
FBSDKAppEventParameterNameTimeBetweenSessions : [NSString stringWithFormat:@"session_quanta_%d", quantaIndex],
FBSDKAppEventParameterLaunchSource: [[self class] getSourceApplication],
FBSDKAppEventParameterNameSessionID : _sessionID ?: @"",
} mutableCopy];
if (_lastSuspendTime) {
params[FBSDKAppEventParameterLogTime] = @(_lastSuspendTime);
}
return [params copy];
}
+ (void)setSourceApplication:(NSString *)sourceApplication openURL:(NSURL *)url
{
[self setSourceApplication:sourceApplication
isFromAppLink:[FBSDKInternalUtility dictionaryFromFBURL:url][@"al_applink_data"] != nil];
}
+ (void)setSourceApplication:(NSString *)sourceApplication isFromAppLink:(BOOL)isFromAppLink
{
g_isOpenedFromAppLink = isFromAppLink;
g_sourceApplication = sourceApplication;
}
+ (NSString *)getSourceApplication
{
NSString *openType = @"Unclassified";
if (g_isOpenedFromAppLink) {
openType = @"AppLink";
}
return (g_sourceApplication ?
[NSString stringWithFormat:@"%@(%@)", openType, g_sourceApplication]
: openType);
}
+ (void)resetSourceApplication
{
g_sourceApplication = nil;
g_isOpenedFromAppLink = NO;
}
+ (void)registerAutoResetSourceApplication
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(resetSourceApplication)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
@end

View File

@ -0,0 +1,48 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "FBSDKAppEvents+Internal.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(UserDataStore)
@interface FBSDKUserDataStore : NSObject
+ (void)setAndHashUserEmail:(nullable NSString *)email
firstName:(nullable NSString *)firstName
lastName:(nullable NSString *)lastName
phone:(nullable NSString *)phone
dateOfBirth:(nullable NSString *)dateOfBirth
gender:(nullable NSString *)gender
city:(nullable NSString *)city
state:(nullable NSString *)state
zip:(nullable NSString *)zip
country:(nullable NSString *)country;
+ (void)setAndHashData:(nullable NSString *)data
forType:(FBSDKAppEventUserDataType)type;
+ (void)setHashData:(nullable NSString *)hashData
forType:(FBSDKAppEventUserDataType)type;
+ (nullable NSString *)getHashedData;
+ (nullable NSString *)getHashedDataForType:(FBSDKAppEventUserDataType)type;
+ (void)clearDataForType:(FBSDKAppEventUserDataType)type;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,199 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKUserDataStore.h"
#import "FBSDKAppEventsUtility.h"
#import "FBSDKLogger.h"
#import "FBSDKSettings.h"
#import "FBSDKUtility.h"
static NSString *const FBSDKUserDataKey = @"com.facebook.appevents.UserDataStore.userData";
static NSMutableDictionary<NSString *, NSString *> *hashedUserData;
static dispatch_queue_t serialQueue;
@implementation FBSDKUserDataStore
+ (void)initialize
{
serialQueue = dispatch_queue_create("com.facebook.appevents.UserDataStore", DISPATCH_QUEUE_SERIAL);
NSString *userData = [[NSUserDefaults standardUserDefaults] stringForKey:FBSDKUserDataKey];
if (userData) {
hashedUserData = (NSMutableDictionary<NSString *, NSString *> *)[NSJSONSerialization JSONObjectWithData:[userData dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingMutableContainers
error:nil];
}
if (!hashedUserData) {
hashedUserData = [[NSMutableDictionary alloc] init];
}
}
+ (void)setAndHashUserEmail:(nullable NSString *)email
firstName:(nullable NSString *)firstName
lastName:(nullable NSString *)lastName
phone:(nullable NSString *)phone
dateOfBirth:(nullable NSString *)dateOfBirth
gender:(nullable NSString *)gender
city:(nullable NSString *)city
state:(nullable NSString *)state
zip:(nullable NSString *)zip
country:(nullable NSString *)country
{
NSMutableDictionary *ud = [[NSMutableDictionary alloc] init];
if (email) {
ud[FBSDKAppEventEmail] = [FBSDKUserDataStore encryptData:email type:FBSDKAppEventEmail];
}
if (firstName) {
ud[FBSDKAppEventFirstName] = [FBSDKUserDataStore encryptData:firstName type:FBSDKAppEventFirstName];
}
if (lastName) {
ud[FBSDKAppEventLastName] = [FBSDKUserDataStore encryptData:lastName type:FBSDKAppEventLastName];
}
if (phone) {
ud[FBSDKAppEventPhone] = [FBSDKUserDataStore encryptData:phone type:FBSDKAppEventPhone];
}
if (dateOfBirth) {
ud[FBSDKAppEventDateOfBirth] = [FBSDKUserDataStore encryptData:dateOfBirth type:FBSDKAppEventDateOfBirth];
}
if (gender) {
ud[FBSDKAppEventGender] = [FBSDKUserDataStore encryptData:gender type:FBSDKAppEventGender];
}
if (city) {
ud[FBSDKAppEventCity] = [FBSDKUserDataStore encryptData:city type:FBSDKAppEventCity];
}
if (state) {
ud[FBSDKAppEventState] = [FBSDKUserDataStore encryptData:state type:FBSDKAppEventState];
}
if (zip) {
ud[FBSDKAppEventZip] = [FBSDKUserDataStore encryptData:zip type:FBSDKAppEventZip];
}
if (country) {
ud[FBSDKAppEventCountry] = [FBSDKUserDataStore encryptData:country type:FBSDKAppEventCountry];
}
dispatch_async(serialQueue, ^{
hashedUserData = [ud mutableCopy];
[[NSUserDefaults standardUserDefaults] setObject:[FBSDKUserDataStore stringByHashedData:hashedUserData]
forKey:FBSDKUserDataKey];
});
}
+ (void)setAndHashData:(nullable NSString *)data
forType:(FBSDKAppEventUserDataType)type
{
[FBSDKUserDataStore setHashData:[FBSDKUserDataStore encryptData:data type:type]
forType:type];
}
+ (void)setHashData:(nullable NSString *)hashData
forType:(FBSDKAppEventUserDataType)type
{
dispatch_async(serialQueue, ^{
if (!hashData) {
[hashedUserData removeObjectForKey:type];
} else {
hashedUserData[type] = hashData;
}
[[NSUserDefaults standardUserDefaults] setObject:[FBSDKUserDataStore stringByHashedData:hashedUserData]
forKey:FBSDKUserDataKey];
});
}
+ (void)clearDataForType:(FBSDKAppEventUserDataType)type
{
[FBSDKUserDataStore setAndHashData:nil forType:type];
}
+ (NSString *)getHashedData
{
__block NSString *hashedUserDataString;
dispatch_sync(serialQueue, ^{
hashedUserDataString = [FBSDKUserDataStore stringByHashedData:hashedUserData];
});
return hashedUserDataString;
}
+ (NSString *)getHashedDataForType:(FBSDKAppEventUserDataType)type
{
__block NSString *hashedData;
dispatch_sync(serialQueue, ^{
hashedData = [hashedUserData objectForKey:type];
});
return hashedData;
}
+ (NSString *)stringByHashedData:(id)hashedData
{
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:hashedData
options:0
error:&error];
if (jsonData) {
return [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
} else {
[FBSDKAppEventsUtility logAndNotify:[NSString stringWithFormat:@"Invalid json object: %@", error]];
return @"";
}
}
+ (NSString *)encryptData:(NSString *)data
type:(FBSDKAppEventUserDataType)type
{
if (data.length == 0 || [FBSDKUserDataStore maybeSHA256Hashed:data]) {
return data;
}
return [FBSDKUtility SHA256Hash:[FBSDKUserDataStore normalizeData:data type:type]];
}
+ (NSString *)normalizeData:(NSString *)data
type:(FBSDKAppEventUserDataType)type
{
NSString *normalizedData = @"";
NSSet<FBSDKAppEventUserDataType> *set = [NSSet setWithArray:
@[FBSDKAppEventEmail, FBSDKAppEventFirstName, FBSDKAppEventLastName, FBSDKAppEventCity, FBSDKAppEventState, FBSDKAppEventCountry]];
if ([set containsObject:type]) {
normalizedData = [data stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
normalizedData = normalizedData.lowercaseString;
} else if ([type isEqualToString:FBSDKAppEventPhone]) {
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^0-9]"
options:NSRegularExpressionCaseInsensitive
error:&error
];
normalizedData = [regex stringByReplacingMatchesInString:data
options:0
range:NSMakeRange(0, data.length)
withTemplate:@""
];
} else if ([type isEqualToString:FBSDKAppEventGender]) {
NSString *temp = [data stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
temp = temp.lowercaseString;
normalizedData = temp.length > 0 ? [temp substringToIndex:1]: @"";
}
return normalizedData;
}
+ (BOOL)maybeSHA256Hashed:(NSString *)data
{
NSRange range = [data rangeOfString:@"[A-Fa-f0-9]{64}" options:NSRegularExpressionSearch];
return (data.length == 64) && (range.location != NSNotFound);
}
@end

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
typedef void (^FBSDKDownloadCompletionBlock)(BOOL success);
@interface FBSDKModelManager : NSObject
+ (void)enable;
+ (nullable NSDictionary *)getRules;
+ (nullable NSString *)getWeightsPath:(NSString *_Nonnull)useCaseKey;
@end
#endif

View File

@ -0,0 +1,233 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKModelManager.h"
#import "FBSDKAddressFilterManager.h"
#import "FBSDKAddressInferencer.h"
#import "FBSDKEventInferencer.h"
#import "FBSDKFeatureExtractor.h"
#import "FBSDKFeatureManager.h"
#import "FBSDKGraphRequest.h"
#import "FBSDKGraphRequestConnection.h"
#import "FBSDKSettings.h"
#import "FBSDKSuggestedEventsIndexer.h"
#import "FBSDKTypeUtility.h"
#import "FBSDKViewHierarchyMacros.h"
#define FBSDK_ML_MODEL_PATH @"models"
static NSString *const MODEL_INFO_KEY= @"com.facebook.sdk:FBSDKModelInfo";
static NSString *const ASSET_URI_KEY = @"asset_uri";
static NSString *const RULES_URI_KEY = @"rules_uri";
static NSString *const THRESHOLDS_KEY = @"thresholds";
static NSString *const USE_CASE_KEY = @"use_case";
static NSString *const VERSION_ID_KEY = @"version_id";
static NSString *const MODEL_DATA_KEY = @"data";
static NSString *const ADDRESS_FILTERING_KEY = @"DATA_DETECTION_ADDRESS";
static NSString *_directoryPath;
static NSMutableDictionary<NSString *, id> *_modelInfo;
@implementation FBSDKModelManager
+ (void)enable
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *languageCode = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode];
// If the languageCode could not be fetched successfully, it's regarded as "en" by default.
if (languageCode && ![languageCode isEqualToString:@"en"]) {
return;
}
NSString *dirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:FBSDK_ML_MODEL_PATH];
if (![[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:NO attributes:NULL error:NULL];
}
_directoryPath = dirPath;
// fetch api
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:[NSString stringWithFormat:@"%@/model_asset", [FBSDKSettings appID]]];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (error) {
return;
}
NSDictionary<NSString *, id> *resultDictionary = [FBSDKTypeUtility dictionaryValue:result];
NSDictionary<NSString *, id> *modelInfo = [self convertToDictionary:resultDictionary[MODEL_DATA_KEY]];
if (!modelInfo) {
return;
}
// update cache
[[NSUserDefaults standardUserDefaults] setObject:modelInfo forKey:MODEL_INFO_KEY];
[FBSDKFeatureManager checkFeature:FBSDKFeatureSuggestedEvents completionBlock:^(BOOL enabled) {
if (enabled) {
[self getModelAndRules:SUGGEST_EVENT_KEY handler:^(BOOL success){
if (success) {
[FBSDKEventInferencer loadWeights];
[FBSDKFeatureExtractor loadRules];
[FBSDKSuggestedEventsIndexer enable];
}
}];
}
}];
[FBSDKFeatureManager checkFeature:FBSDKFeaturePIIFiltering completionBlock:^(BOOL enabled) {
if (enabled) {
[self getModelAndRules:ADDRESS_FILTERING_KEY handler:^(BOOL success){
if (success) {
[FBSDKAddressInferencer loadWeights];
[FBSDKAddressInferencer initializeDenseFeature];
[FBSDKAddressFilterManager enable];
}
}];
}
}];
}];
});
}
+ (void)getModelAndRules:(NSString *)useCaseKey
handler:(FBSDKDownloadCompletionBlock)handler
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
_modelInfo = [[NSUserDefaults standardUserDefaults] objectForKey:MODEL_INFO_KEY];
if (!_modelInfo || !_directoryPath) {
if (handler) {
handler(NO);
return;
}
}
NSDictionary<NSString *, id> *model = [_modelInfo objectForKey:useCaseKey];
if (!model) {
if (handler) {
handler(NO);
return;
}
}
// clear old model files
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_directoryPath error:nil];
NSString *prefixWithVersion = [NSString stringWithFormat:@"%@_%@", useCaseKey, model[VERSION_ID_KEY]];
for (NSString *file in files) {
if ([file hasPrefix:useCaseKey] && ![file hasPrefix:prefixWithVersion]) {
[[NSFileManager defaultManager] removeItemAtPath:[_directoryPath stringByAppendingPathComponent:file] error:nil];
}
}
// download model asset
NSString *assetUrlString = [model objectForKey:ASSET_URI_KEY];
NSString *assetFilePath;
if (assetUrlString.length > 0) {
assetFilePath = [_directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@.weights", useCaseKey, model[VERSION_ID_KEY]]];
[self download:assetUrlString filePath:assetFilePath queue:queue group:group];
}
// download rules
NSString *rulesUrlString = [model objectForKey:RULES_URI_KEY];
NSString *rulesFilePath;
// rules are optional and rulesUrlString may be empty
if (rulesUrlString.length > 0) {
rulesFilePath = [_directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@.rules", useCaseKey, model[VERSION_ID_KEY]]];
[self download:rulesUrlString filePath:rulesFilePath queue:queue group:group];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (handler) {
if ([[NSFileManager defaultManager] fileExistsAtPath:assetFilePath] && (!rulesUrlString || (rulesUrlString && [[NSFileManager defaultManager] fileExistsAtPath:rulesFilePath]))) {
handler(YES);
return;
}
handler(NO);
}
});
}
+ (void)download:(NSString *)urlString
filePath:(NSString *)filePath
queue:(dispatch_queue_t)queue
group:(dispatch_group_t)group
{
if (!filePath || [[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
return;
}
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:urlString];
NSData *urlData = [NSData dataWithContentsOfURL:url];
if (urlData) {
[urlData writeToFile:filePath atomically:YES];
}
});
}
+ (nullable NSMutableDictionary<NSString *, id> *)convertToDictionary:(NSArray<NSDictionary<NSString *, id> *> *)models
{
if ([models count] == 0) {
return nil;
}
NSMutableDictionary<NSString *, id> *modelInfo = [NSMutableDictionary dictionary];
for (NSDictionary<NSString *, id> *model in models) {
if (model[USE_CASE_KEY]) {
[modelInfo addEntriesFromDictionary:@{model[USE_CASE_KEY]:model}];
}
}
return modelInfo;
}
+ (nullable NSDictionary *)getRules
{
NSDictionary<NSString *, id> *cachedModelInfo = [[NSUserDefaults standardUserDefaults] objectForKey:MODEL_INFO_KEY];
if (!cachedModelInfo) {
return nil;
}
NSDictionary<NSString *, id> *model = [cachedModelInfo objectForKey:SUGGEST_EVENT_KEY];
if (model && model[VERSION_ID_KEY]) {
NSString *filePath = [_directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@.rules", SUGGEST_EVENT_KEY, model[VERSION_ID_KEY]]];
if (filePath) {
NSData *ruelsData = [NSData dataWithContentsOfFile:filePath];
NSDictionary *rules = [NSJSONSerialization JSONObjectWithData:ruelsData options:0 error:nil];
return rules;
}
}
return nil;
}
+ (nullable NSString *)getWeightsPath:(NSString *_Nonnull)useCaseKey
{
NSDictionary<NSString *, id> *cachedModelInfo = [[NSUserDefaults standardUserDefaults] objectForKey:MODEL_INFO_KEY];
if (!cachedModelInfo || !_directoryPath) {
return nil;
}
NSDictionary<NSString *, id> *model = [cachedModelInfo objectForKey:useCaseKey];
if (model && model[VERSION_ID_KEY]) {
return [_directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@.weights", useCaseKey, model[VERSION_ID_KEY]]];
}
return nil;
}
@end
#endif

View File

@ -0,0 +1,310 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#include <float.h>
#include <math.h>
#include <stdint.h>
#include <unordered_map>
#include <unordered_set>
#import <Accelerate/Accelerate.h>
#include "FBSDKStandaloneModel.hpp"
#define SEQ_LEN 128
#define ALPHABET_SIZE 256
#define EMBEDDING_SIZE 64
#define DENSE_FEATURE_LEN 30
const int CONV_BLOCKS[3][3] = {{32, 2, SEQ_LEN - 1}, {32, 3, SEQ_LEN - 2}, {32, 5, SEQ_LEN - 4}};
namespace mat1 {
static void relu(float *data, int len) {
float min = 0;
float max = FLT_MAX;
vDSP_vclip(data, 1, &min, &max, data, 1, len);
}
static void concatenate(float *dst, float *a, float *b, int a_len, int b_len) {
memcpy(dst, a, a_len * sizeof(float));
memcpy(dst + a_len, b, b_len * sizeof(float));
}
static void softmax(float *data, int n) {
int i = 0;
float max = FLT_MIN;
float sum = 0;
for (i = 0; i < n; i++) {
if (data[i] > max) {
max = data[i];
}
}
for (i = 0; i < n; i++){
data[i] = expf(data[i] - max);
}
for (i = 0; i < n; i++){
sum += data[i];
}
for (i = 0; i < n; i++){
data[i] = data[i] / sum;
}
}
static float* embedding(int *a, float *b, int n_examples, int seq_length, int embedding_size) {
int i,j,k,val;
float* res = (float *)malloc(sizeof(float) * (n_examples * seq_length * embedding_size));
for (i = 0; i < n_examples; i++) {
for (j = 0; j < seq_length; j++) {
val = a[i * seq_length + j];
for (k = 0; k < embedding_size; k++) {
res[(embedding_size * seq_length) * i + embedding_size * j + k] = b[val * embedding_size + k];
}
}
}
return res;
}
/*
a shape: n_examples, in_vector_size
b shape: n_examples, out_vector_size
c shape: out_vector_size
return shape: n_examples, out_vector_size
*/
static float* dense(float *a, float *b, float *c, int n_examples, int in_vector_size, int out_vector_size) {
int i,j;
float *m_res = (float *)malloc(sizeof(float) * (n_examples * out_vector_size));
vDSP_mmul(a, 1, b, 1, m_res, 1, n_examples, out_vector_size, in_vector_size);
for (i = 0; i < n_examples; i++) {
for (j = 0; j < out_vector_size; j++) {
m_res[i * out_vector_size + j] += c[j];
}
}
return m_res;
}
/*
x shape: n_examples, seq_len, input_size
w shape: kernel_size, input_size, output_size
return shape: n_examples, seq_len - kernel_size + 1, output_size
*/
static float* conv1D(float *x, float *w, int n_examples, int seq_len, int input_size, int kernel_size, int output_size) {
int n, o, i, k, m;
float sum;
float *res = (float *)malloc(sizeof(float) * (n_examples * (seq_len - kernel_size + 1) * output_size));
float *temp_x = (float *)malloc(sizeof(float) * (kernel_size * input_size));
float *temp_w = (float *)malloc(sizeof(float) * (kernel_size * input_size));
for (n = 0; n < n_examples; n++){
for (o = 0; o < output_size; o++){
for (i = 0; i < seq_len - kernel_size + 1; i++) {
sum = 0;
for (m = 0; m < kernel_size; m++) {
for (k = 0; k < input_size; k++) {
temp_x[m * input_size + k] = x[n * (seq_len * input_size) + (m + i) * input_size + k];
temp_w[m * input_size + k] = w[(m * input_size + k) * output_size + o];
}
}
vDSP_dotpr(temp_x, 1, temp_w, 1, &sum, kernel_size * input_size);
res[(n * (output_size * (seq_len - kernel_size + 1)) + i * output_size + o)] = sum;
}
}
}
free(temp_x);
free(temp_w);
return res;
}
/*
input shape: n_examples, len, n_channel
return shape: n_examples, len - pool_size + 1, n_channel
*/
static float* maxPool1D(float *input, int n_examples, int input_len, int n_channel, int pool_size) {
int res_len = input_len - pool_size + 1;
float* res = (float *)calloc(n_examples * res_len * n_channel, sizeof(float));
for (int n = 0; n < n_examples; n++) {
for (int c = 0; c < n_channel; c++) {
for (int i = 0; i < res_len; i++) {
for (int r = i; r < i + pool_size; r++) {
int res_pos = n * (n_channel * res_len) + i * n_channel + c;
int input_pos = n * (n_channel * input_len) + r * n_channel + c;
if (r == i) {
res[res_pos] = input[input_pos];
} else {
res[res_pos] = fmax(res[res_pos], input[input_pos]);
}
}
}
}
}
return res;
}
static int* vectorize(const char *texts, int str_len, int max_len) {
int *res = (int *)malloc(sizeof(int) * max_len);
for (int i = 0; i < max_len; i++) {
if (i < str_len){
res[i] = static_cast<unsigned char>(texts[i]);
} else {
res[i] = 0;
}
}
return res;
}
/*
input shape: m, n
return shape: n, m
*/
static float* transpose2D(float *input, int m, int n) {
float *transposed = (float *)malloc(sizeof(float) * m * n);
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++) {
transposed[j * m + i] = input[i * n + j];
}
}
return transposed;
}
/*
input shape: m, n, p
return shape: p, n, m
*/
static float* transpose3D(float *input, int64_t m, int n, int p) {
float *transposed = (float *)malloc((size_t)(sizeof(float) * m * n * p));
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++) {
for (int k = 0; k < p; k++) {
transposed[k * m * n + j * m + i] = input[i * n * p + j * p + k];
}
}
}
return transposed;
}
static float* add(float *a, float *b, int m, int n, int p) {
for(int i = 0; i < m * n; i++){
for(int j = 0; j < p; j++){
a[i * p + j] += b[j];
}
}
return a;
}
static float* predictOnText(const char *texts, std::unordered_map<std::string, mat::MTensor>& weights, float *df) {
int *x;
float *embed_x;
float *dense1_x;
float *dense2_x;
float *dense3_x;
float *c1;
float *c2;
float *c3;
float *ca;
float *cb;
float *cc;
mat::MTensor& embed_t = weights.at("embed.weight");
mat::MTensor& conv1w_t = weights.at("convs.0.weight"); // (32, 64, 2)
mat::MTensor& conv2w_t = weights.at("convs.1.weight");
mat::MTensor& conv3w_t = weights.at("convs.2.weight");
mat::MTensor& conv1b_t = weights.at("convs.0.bias");
mat::MTensor& conv2b_t = weights.at("convs.1.bias");
mat::MTensor& conv3b_t = weights.at("convs.2.bias");
mat::MTensor& fc1w_t = weights.at("fc1.weight"); // (128, 126)
mat::MTensor& fc1b_t = weights.at("fc1.bias"); // 128
mat::MTensor& fc2w_t = weights.at("fc2.weight"); // (64, 128)
mat::MTensor& fc2b_t = weights.at("fc2.bias"); // 64
mat::MTensor& fc3w_t = weights.at("fc3.weight"); // (2, 64) or (4, 64)
mat::MTensor& fc3b_t = weights.at("fc3.bias"); // 2 or 4
float *embed_weight = embed_t.data<float>();
float *convs_0_weight = transpose3D(conv1w_t.data<float>(), (int)conv1w_t.size(0), (int)conv1w_t.size(1), (int)conv1w_t.size(2)); // (2, 64, 32)
float *convs_1_weight = transpose3D(conv2w_t.data<float>(), (int)conv2w_t.size(0), (int)conv2w_t.size(1), (int)conv2w_t.size(2));
float *convs_2_weight = transpose3D(conv3w_t.data<float>(), (int)conv3w_t.size(0), (int)conv3w_t.size(1), (int)conv3w_t.size(2));
float *convs_0_bias = conv1b_t.data<float>();
float *convs_1_bias = conv2b_t.data<float>();
float *convs_2_bias = conv3b_t.data<float>();
float *fc1_weight = transpose2D(fc1w_t.data<float>(), (int)fc1w_t.size(0), (int)fc1w_t.size(1));
float *fc2_weight = transpose2D(fc2w_t.data<float>(), (int)fc2w_t.size(0), (int)fc2w_t.size(1));
float *fc3_weight = transpose2D(fc3w_t.data<float>(), (int)fc3w_t.size(0), (int)fc3w_t.size(1));
float *fc1_bias = fc1b_t.data<float>();
float *fc2_bias = fc2b_t.data<float>();
float *fc3_bias = fc3b_t.data<float>();
// vectorize text
x = vectorize(texts, (int)strlen(texts), SEQ_LEN);
// embedding
embed_x = embedding(x, embed_weight, 1, SEQ_LEN, EMBEDDING_SIZE); // (1, 128, 64)
free(x);
// conv1D
c1 = conv1D(embed_x, convs_0_weight, 1, SEQ_LEN, EMBEDDING_SIZE, (int)conv1w_t.size(2), (int)conv1w_t.size(0)); // (1, 127, 32) CONV_BLOCKS[0][1], CONV_BLOCKS[0][0]
c2 = conv1D(embed_x, convs_1_weight, 1, SEQ_LEN, EMBEDDING_SIZE, (int)conv2w_t.size(2), (int)conv2w_t.size(0)); // (1, 126, 32)
c3 = conv1D(embed_x, convs_2_weight, 1, SEQ_LEN, EMBEDDING_SIZE, (int)conv3w_t.size(2), (int)conv3w_t.size(0)); // (1, 124, 32)
free(embed_x);
// add bias
add(c1, convs_0_bias, 1, (int)(SEQ_LEN - conv1w_t.size(2) + 1), (int)conv1w_t.size(0));
add(c2, convs_1_bias, 1, (int)(SEQ_LEN - conv2w_t.size(2) + 1), (int)conv2w_t.size(0));
add(c3, convs_2_bias, 1, (int)(SEQ_LEN - conv3w_t.size(2) + 1), (int)conv3w_t.size(0));
// relu
relu(c1, (int)(SEQ_LEN - conv1w_t.size(2) + 1) * (int)conv1w_t.size(0));
relu(c2, (int)(SEQ_LEN - conv2w_t.size(2) + 1) * (int)conv2w_t.size(0));
relu(c3, (int)(SEQ_LEN - conv3w_t.size(2) + 1) * (int)conv3w_t.size(0));
// max pooling
ca = maxPool1D(c1, 1, (int)(SEQ_LEN - conv1w_t.size(2) + 1), (int)conv1w_t.size(0), (int)(SEQ_LEN - conv1w_t.size(2) + 1)); // (1, 1, 32)
cb = maxPool1D(c2, 1, (int)(SEQ_LEN - conv2w_t.size(2) + 1), (int)conv2w_t.size(0), (int)(SEQ_LEN - conv2w_t.size(2) + 1)); // (1, 1, 32)
cc = maxPool1D(c3, 1, (int)(SEQ_LEN - conv3w_t.size(2) + 1), (int)conv3w_t.size(0), (int)(SEQ_LEN - conv3w_t.size(2) + 1)); // (1, 1, 32)
free(c1);
free(c2);
free(c3);
// concatenate
float *concat = (float *)malloc((size_t)(sizeof(float) * (conv1w_t.size(0) + conv2w_t.size(0) + conv3w_t.size(0) + 30)));
concatenate(concat, ca, cb, (int)conv1w_t.size(0), (int)conv2w_t.size(0));
concatenate(concat + conv1w_t.size(0) + conv2w_t.size(0), cc, df, (int)conv3w_t.size(0), 30);
free(ca);
free(cb);
free(cc);
// dense + relu
dense1_x = dense(concat, fc1_weight, fc1_bias, 1, (int)fc1w_t.size(1), (int)fc1w_t.size(0));
free(concat);
relu(dense1_x, (int)fc1b_t.size(0));
dense2_x = dense(dense1_x, fc2_weight, fc2_bias, 1, (int)fc2w_t.size(1), (int)fc2w_t.size(0));
relu(dense2_x, (int)fc2b_t.size(0));
free(dense1_x);
dense3_x = dense(dense2_x, fc3_weight, fc3_bias, 1, (int)fc3w_t.size(1), (int)fc3w_t.size(0));
free(dense2_x);
softmax(dense3_x, (int)fc3b_t.size(0));
return dense3_x;
}
}
#endif

View File

@ -0,0 +1,31 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
@interface FBSDKModelUtility : NSObject
+ (NSString *)normalizeText:(NSString *)text;
@end
#endif

View File

@ -0,0 +1,38 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "FBSDKModelUtility.h"
@implementation FBSDKModelUtility : NSObject
+ (NSString *)normalizeText:(NSString *)text
{
NSMutableArray *tokens = [[text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] mutableCopy];
[tokens removeObject:@""];
return [tokens componentsJoinedByString: @" "];
}
@end
#endif

View File

@ -0,0 +1,152 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#include <cassert>
#include <cmath>
#include <cstring>
#include <iostream>
#include <memory>
#include <stddef.h>
#include <stdint.h>
#include <unordered_map>
#include <vector>
#import <Accelerate/Accelerate.h>
// minimal aten implementation
#define MAT_ALWAYS_INLINE inline __attribute__((always_inline))
namespace mat {
template <typename T, size_t N>
class MTensorAccessor {
public:
MAT_ALWAYS_INLINE
MTensorAccessor(T* data, const int64_t* sizes, const int64_t* strides)
: data_(data), sizes_(sizes), strides_(strides) {}
MAT_ALWAYS_INLINE MTensorAccessor<T, N - 1> operator[](int64_t i) {
return MTensorAccessor<T, N - 1>(
this->data_ + this->strides_[0] * i,
this->sizes_ + 1,
this->strides_ + 1);
}
T* data_;
private:
const int64_t* sizes_;
const int64_t* strides_;
};
template <typename T>
class MTensorAccessor<T, 1> {
public:
MAT_ALWAYS_INLINE
MTensorAccessor(T* data, const int64_t* sizes, const int64_t* strides)
: data_(data), sizes_(sizes), strides_(strides) {}
MAT_ALWAYS_INLINE T& operator[](int64_t i) {
// assume stride==1 in innermost dimension.
// DCHECK_EQ(strides_[0], 1);
return this->data_[i];
}
T* data_;
private:
const int64_t* sizes_;
const int64_t* strides_;
};
static void* MAllocateMemory(size_t nbytes) {
void* ptr = nullptr;
assert(nbytes > 0);
#ifdef __ANDROID__
ptr = memalign(64, nbytes);
#else
const int ret = posix_memalign(&ptr, 64, nbytes);
(void)ret;
assert(ret == 0);
#endif
return ptr;
}
static void MFreeMemory(void* ptr) {
free(ptr);
}
static void MCheckPtr(void* ptr) {
if (ptr) {
MFreeMemory(ptr);
}
}
class MTensor {
public:
MTensor(){};
MTensor(const std::vector<int64_t>& sizes) {
auto strides = std::vector<int64_t>(sizes.size());
strides[strides.size() - 1] = 1;
for (auto i = static_cast<int32_t>(strides.size()) - 2; i >= 0; --i) {
strides[i] = strides[i + 1] * sizes[i + 1];
}
strides_ = strides;
sizes_ = sizes;
// assume float32 storage.
size_t nbytes = sizeof(float);
for (auto size : sizes) {
nbytes *= size;
}
storage_ = std::shared_ptr<void>(MAllocateMemory(nbytes), MCheckPtr);
}
int64_t size(int dim) {
return sizes_[dim];
}
const std::vector<int64_t>& sizes() const {
return sizes_;
}
const std::vector<int64_t>& strides() const {
return strides_;
}
template <typename T>
T* data() {
return static_cast<T*>(storage_.get());
}
template <typename T, size_t N>
MTensorAccessor<T, N> accessor() {
return MTensorAccessor<T, N>(data<T>(), sizes().data(), strides().data());
}
private:
std::vector<int64_t> sizes_;
std::vector<int64_t> strides_;
std::shared_ptr<void> storage_;
};
static MTensor mempty(const std::vector<int64_t>& sizes) {
return MTensor(sizes);
}
} // namespace mat
#endif

View File

@ -0,0 +1,32 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
@interface FBSDKAddressFilterManager : NSObject
+ (void)enable;
+ (nullable NSDictionary<NSString *, id> *)processParameters:(nullable NSDictionary<NSString *, id> *)parameters;
@end
#endif

View File

@ -0,0 +1,69 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAddressFilterManager.h"
#import "FBSDKAddressInferencer.h"
#import "FBSDKBasicUtility.h"
#import "FBSDKGateKeeperManager.h"
#import "FBSDKSettings.h"
#import "FBSDKTypeUtility.h"
static BOOL isAddressFilterEnabled = NO;
static BOOL isSampleEnabled = NO;
@implementation FBSDKAddressFilterManager
+ (void)enable
{
isAddressFilterEnabled = YES;
isSampleEnabled = [FBSDKGateKeeperManager boolForKey:@"FBSDKFeatureAddressDetectionSample" defaultValue:false];
}
+ (nullable NSDictionary<NSString *, id> *)processParameters:(nullable NSDictionary<NSString *, id> *)parameters
{
if (!isAddressFilterEnabled || parameters.count == 0) {
return parameters;
}
NSMutableDictionary<NSString *, id> *params = [NSMutableDictionary dictionaryWithDictionary:parameters];
NSMutableDictionary<NSString *, id> *addressParams = [NSMutableDictionary dictionary];
for (NSString *key in [parameters keyEnumerator]) {
NSString *valueString =[FBSDKTypeUtility stringValue:parameters[key]];
BOOL shouldFilter = [FBSDKAddressInferencer shouldFilterParam:valueString];
if (shouldFilter) {
[addressParams setObject:isSampleEnabled ? valueString : @"" forKey:key];
[params removeObjectForKey:key];
}
}
if ([addressParams count] > 0) {
NSString *addressParamsJSONString = [FBSDKBasicUtility JSONStringForObject:addressParams
error:NULL
invalidObjectHandler:NULL];
[FBSDKBasicUtility dictionary:params setObject:addressParamsJSONString forKey:@"_onDeviceParams"];
}
return [params copy];
}
@end
#endif

View File

@ -0,0 +1,37 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKAddressInferencer : NSObject
+ (void)initializeDenseFeature;
+ (void)loadWeights;
+ (BOOL)shouldFilterParam:(nullable NSString *)param;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,213 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAddressInferencer.h"
#import "FBSDKModelManager.h"
#import "FBSDKModelRuntime.hpp"
#import "FBSDKModelUtility.h"
#import "FBSDKStandaloneModel.hpp"
#include<stdexcept>
static NSString *const MODEL_INFO_KEY= @"com.facebook.sdk:FBSDKModelInfo";
static NSString *const THRESHOLDS_KEY = @"thresholds";
static NSString *const DATA_DETECTION_ADDRESS_KEY = @"DATA_DETECTION_ADDRESS";
static NSDictionary<NSString *, NSArray *> *const WEIGHTS_INFO = @{@"embed.weight" : @[@(256), @(64)],
@"convs.0.weight" : @[@(32), @(64), @(2)],
@"convs.0.bias" : @[@(32)],
@"convs.1.weight" : @[@(32), @(64), @(3)],
@"convs.1.bias" : @[@(32)],
@"convs.2.weight" : @[@(32), @(64), @(5)],
@"convs.2.bias" : @[@(32)],
@"fc1.weight": @[@(128), @(126)],
@"fc1.bias": @[@(128)],
@"fc2.weight": @[@(64), @(128)],
@"fc2.bias": @[@(64)],
@"fc3.weight": @[@(2), @(64)],
@"fc3.bias": @[@(2)]};
static NSDictionary<NSString *, NSString *> *const WEIGHTS_KEYS = @{@"embedding.weight": @"embed.weight",
@"dense1.weight": @"fc1.weight",
@"dense2.weight": @"fc2.weight",
@"dense3.weight": @"fc3.weight",
@"dense1.bias": @"fc1.bias",
@"dense2.bias": @"fc2.bias",
@"dense3.bias": @"fc3.bias"};
@implementation FBSDKAddressInferencer : NSObject
static std::unordered_map<std::string, mat::MTensor> _weights;
static std::vector<float> _denseFeature;
+ (void)initializeDenseFeature
{
std::vector<float> dense_feature(30);
std::fill(dense_feature.begin(), dense_feature.end(), 0);
_denseFeature = dense_feature;
}
+ (void)loadWeights
{
NSString *path = [FBSDKModelManager getWeightsPath:DATA_DETECTION_ADDRESS_KEY];
if (!path) {
return;
}
NSData *latestData = [NSData dataWithContentsOfFile:path
options:NSDataReadingMappedIfSafe
error:nil];
if (!latestData) {
return;
}
std::unordered_map<std::string, mat::MTensor> weights = [self loadWeights:latestData];
if ([self validateWeights:weights]) {
_weights = weights;
}
}
+ (bool)validateWeights: (std::unordered_map<std::string, mat::MTensor>) weights
{
if (WEIGHTS_INFO.count != weights.size()) {
return false;
}
try {
for (NSString *key in WEIGHTS_INFO) {
if (weights.count(std::string([key UTF8String])) == 0) {
return false;
}
mat::MTensor tensor = weights[std::string([key UTF8String])];
const std::vector<int64_t>& actualSize = tensor.sizes();
NSArray *expectedSize = WEIGHTS_INFO[key];
if (actualSize.size() != expectedSize.count) {
return false;
}
for (int i = 0; i < expectedSize.count; i++) {
if((int)actualSize[i] != (int)[expectedSize[i] intValue]) {
return false;
}
}
}
} catch (const std::exception &e) {
return false;
}
return true;
}
+ (std::unordered_map<std::string, mat::MTensor>)loadWeights:(NSData *)weightsData{
std::unordered_map<std::string, mat::MTensor> weights;
const void *data = weightsData.bytes;
NSUInteger totalLength = weightsData.length;
int totalFloats = 0;
if (weightsData.length < 4) {
// Make sure data length is valid
return weights;
}
try {
int length;
memcpy(&length, data, 4);
if (length + 4 > totalLength) {
// Make sure data length is valid
return weights;
}
char *json = (char *)data + 4;
NSDictionary<NSString *, id> *info = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:json length:length]
options:0
error:nil];
NSArray<NSString *> *keys = [[info allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *key1, NSString *key2) {
return [key1 compare:key2];
}];
float *floats = (float *)(json + length);
for (NSString *key in keys) {
NSString *finalKey = key;
NSString *mapping = [WEIGHTS_KEYS objectForKey:key];
if (mapping) {
finalKey = mapping;
}
std::string s_name([finalKey UTF8String]);
std::vector<int64_t> v_shape;
NSArray<NSString *> *shape = [info objectForKey:key];
int count = 1;
for (NSNumber *_s in shape) {
int i = [_s intValue];
v_shape.push_back(i);
count *= i;
}
totalFloats += count;
if ((4 + length + totalFloats * 4) > totalLength) {
// Make sure data length is valid
break;
}
mat::MTensor tensor = mat::mempty(v_shape);
float *tensor_data = tensor.data<float>();
memcpy(tensor_data, floats, sizeof(float) * count);
floats += count;
weights[s_name] = tensor;
}
} catch (const std::exception &e) {}
return weights;
}
+ (BOOL)shouldFilterParam:(nullable NSString *)param
{
if (!param || _weights.size() == 0 || _denseFeature.size() == 0) {
return false;
}
NSString *text = [FBSDKModelUtility normalizeText:param];
const char *bytes = [text UTF8String];
if ((int)strlen(bytes) == 0) {
return false;
}
float *predictedRaw;
NSMutableDictionary<NSString *, id> *modelInfo = [[NSUserDefaults standardUserDefaults] objectForKey:MODEL_INFO_KEY];
if (!modelInfo) {
return false;
}
NSDictionary<NSString *, id> * addressModelInfo = [modelInfo objectForKey:DATA_DETECTION_ADDRESS_KEY];
if (!addressModelInfo) {
return false;
}
NSMutableArray *thresholds = [addressModelInfo objectForKey:THRESHOLDS_KEY];
float threshold = [thresholds[0] floatValue];
try {
predictedRaw = mat1::predictOnText(bytes, _weights, &_denseFeature[0]);
if (!predictedRaw[1]) {
return false;
}
return predictedRaw[1] >= threshold;
} catch (const std::exception &e) {
return false;
}
}
@end
#endif

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKRestrictiveDataFilterManager : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (void)enable;
+ (void)updateFilters:(nullable NSDictionary<NSString *, id> *)restrictiveParams;
+ (nullable NSDictionary<NSString *, id> *)processParameters:(nullable NSDictionary<NSString *, id> *)parameters
eventName:(NSString *)eventName;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,146 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKRestrictiveDataFilterManager.h"
#import "FBSDKBasicUtility.h"
#import "FBSDKTypeUtility.h"
static NSString *const RESTRICTIVE_PARAM_KEY = @"restrictive_param";
@interface FBSDKRestrictiveEventFilter : NSObject
@property (nonatomic, readonly, copy) NSString *eventName;
@property (nonatomic, readonly, copy) NSDictionary<NSString *, id> *restrictiveParams;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
-(instancetype)initWithEventName:(NSString *)eventName
restrictiveParams:(NSDictionary<NSString *, id> *)restrictiveParams;
@end
@implementation FBSDKRestrictiveEventFilter
-(instancetype)initWithEventName:(NSString *)eventName
restrictiveParams:(NSDictionary<NSString *, id> *)restrictiveParams
{
self = [super init];
if (self) {
_eventName = eventName;
_restrictiveParams = restrictiveParams;
}
return self;
}
@end
@implementation FBSDKRestrictiveDataFilterManager
static BOOL isRestrictiveEventFilterEnabled = NO;
static NSMutableArray<FBSDKRestrictiveEventFilter *> *_params;
+ (void)updateFilters:(nullable NSDictionary<NSString *, id> *)restrictiveParams
{
if (!isRestrictiveEventFilterEnabled) {
return;
}
if (restrictiveParams.count > 0) {
[_params removeAllObjects];
NSMutableArray<FBSDKRestrictiveEventFilter *> *eventFilterArray = [NSMutableArray array];
for (NSString *eventName in restrictiveParams.allKeys) {
NSDictionary<NSString *, id> *eventInfo = restrictiveParams[eventName];
if (!eventInfo) {
return;
}
if (eventInfo[RESTRICTIVE_PARAM_KEY]) {
FBSDKRestrictiveEventFilter *restrictiveEventFilter = [[FBSDKRestrictiveEventFilter alloc] initWithEventName:eventName
restrictiveParams:eventInfo[RESTRICTIVE_PARAM_KEY]];
[eventFilterArray addObject:restrictiveEventFilter];
}
}
_params = eventFilterArray;
}
}
+ (nullable NSString *)getMatchedDataTypeWithEventName:(NSString *)eventName
paramKey:(NSString *)paramKey
{
// match by params in custom events with event name
for (FBSDKRestrictiveEventFilter *filter in _params) {
if ([filter.eventName isEqualToString:eventName]) {
NSString *type = [FBSDKTypeUtility stringValue:filter.restrictiveParams[paramKey]];
if (type) {
return type;
}
}
}
return nil;
}
+ (NSDictionary<NSString *,id> *)processParameters:(NSDictionary<NSString *,id> *)parameters
eventName:(NSString *)eventName
{
if (!isRestrictiveEventFilterEnabled) {
return parameters;
}
if (parameters) {
NSMutableDictionary<NSString *, id> *params = [NSMutableDictionary dictionaryWithDictionary:parameters];
NSMutableDictionary<NSString *, NSString *> *restrictedParams = [NSMutableDictionary dictionary];
for (NSString *key in [parameters keyEnumerator]) {
NSString *type = [FBSDKRestrictiveDataFilterManager getMatchedDataTypeWithEventName:eventName
paramKey:key];
if (type) {
[restrictedParams setObject:type forKey:key];
[params removeObjectForKey:key];
}
}
if ([[restrictedParams allKeys] count] > 0) {
NSString *restrictedParamsJSONString = [FBSDKBasicUtility JSONStringForObject:restrictedParams
error:NULL
invalidObjectHandler:NULL];
[FBSDKBasicUtility dictionary:params setObject:restrictedParamsJSONString forKey:@"_restrictedParams"];
}
return [params copy];
}
return nil;
}
+ (void)enable
{
isRestrictiveEventFilterEnabled = YES;
}
#pragma mark Helper functions
+ (BOOL)isMatchedWithPattern:(NSString *)pattern
text:(NSString *)text
{
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSUInteger matches = [regex numberOfMatchesInString:text options:0 range:NSMakeRange(0, text.length)];
return matches > 0;
}
@end

View File

@ -0,0 +1,38 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKEventInferencer : NSObject
+ (void)loadWeights;
+ (NSDictionary<NSString *, NSString *> *)predict:(NSString *)buttonText
viewTree:(NSMutableDictionary<NSString *, id> *)viewTree
withLog:(BOOL)isPrint;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,227 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKEventInferencer.h"
#import <Foundation/Foundation.h>
#import "FBSDKFeatureExtractor.h"
#import "FBSDKModelManager.h"
#import "FBSDKModelRuntime.hpp"
#import "FBSDKModelUtility.h"
#import "FBSDKViewHierarchyMacros.h"
#include<stdexcept>
static NSString *const MODEL_INFO_KEY= @"com.facebook.sdk:FBSDKModelInfo";
static NSString *const THRESHOLDS_KEY = @"thresholds";
static NSString *const SUGGESTED_EVENT[4] = {@"fb_mobile_add_to_cart", @"fb_mobile_complete_registration", @"other", @"fb_mobile_purchase"};
static NSDictionary<NSString *, NSString *> *const DEFAULT_PREDICTION = @{SUGGEST_EVENT_KEY: SUGGESTED_EVENTS_OTHER};
static NSDictionary<NSString *, NSArray *> *const WEIGHTS_INFO = @{@"embed.weight" : @[@(256), @(64)],
@"convs.0.weight" : @[@(32), @(64), @(2)],
@"convs.0.bias" : @[@(32)],
@"convs.1.weight" : @[@(32), @(64), @(3)],
@"convs.1.bias" : @[@(32)],
@"convs.2.weight" : @[@(32), @(64), @(5)],
@"convs.2.bias" : @[@(32)],
@"fc1.weight": @[@(128), @(126)],
@"fc1.bias": @[@(128)],
@"fc2.weight": @[@(64), @(128)],
@"fc2.bias": @[@(64)],
@"fc3.weight": @[@(4), @(64)],
@"fc3.bias": @[@(4)]};
static std::unordered_map<std::string, mat::MTensor> _weights;
@implementation FBSDKEventInferencer : NSObject
+ (void)loadWeights
{
NSString *path = [FBSDKModelManager getWeightsPath:SUGGEST_EVENT_KEY];
if (!path) {
return;
}
NSData *latestData = [NSData dataWithContentsOfFile:path
options:NSDataReadingMappedIfSafe
error:nil];
if (!latestData) {
return;
}
std::unordered_map<std::string, mat::MTensor> weights = [self loadWeights:latestData];
if ([self validateWeights:weights]) {
_weights = weights;
}
}
+ (bool)validateWeights: (std::unordered_map<std::string, mat::MTensor>) weights
{
if (WEIGHTS_INFO.count != weights.size()) {
return false;
}
try {
for (NSString *key in WEIGHTS_INFO) {
if (weights.count(std::string([key UTF8String])) == 0) {
return false;
}
mat::MTensor tensor = weights[std::string([key UTF8String])];
const std::vector<int64_t>& actualSize = tensor.sizes();
NSArray *expectedSize = WEIGHTS_INFO[key];
if (actualSize.size() != expectedSize.count) {
return false;
}
for (int i = 0; i < expectedSize.count; i++) {
if((int)actualSize[i] != (int)[expectedSize[i] intValue]) {
return false;
}
}
}
} catch (const std::exception &e) {
return false;
}
return true;
}
+ (std::unordered_map<std::string, mat::MTensor>)loadWeights:(NSData *)weightsData{
std::unordered_map<std::string, mat::MTensor> weights;
try {
const void *data = weightsData.bytes;
NSUInteger totalLength = weightsData.length;
int totalFloats = 0;
if (weightsData.length < 4) {
// Make sure data length is valid
return weights;
}
int length;
memcpy(&length, data, 4);
if (length + 4 > totalLength) {
// Make sure data length is valid
return weights;
}
char *json = (char *)data + 4;
NSDictionary<NSString *, id> *info = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:json length:length]
options:0
error:nil];
NSArray<NSString *> *keys = [[info allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *key1, NSString *key2) {
return [key1 compare:key2];
}];
float *floats = (float *)(json + length);
for (NSString *key in keys) {
std::string s_name([key UTF8String]);
std::vector<int64_t> v_shape;
NSArray<NSString *> *shape = [info objectForKey:key];
int count = 1;
for (NSNumber *_s in shape) {
int i = [_s intValue];
v_shape.push_back(i);
count *= i;
}
totalFloats += count;
if ((4 + length + totalFloats * 4) > totalLength) {
// Make sure data length is valid
break;
}
mat::MTensor tensor = mat::mempty(v_shape);
float *tensor_data = tensor.data<float>();
memcpy(tensor_data, floats, sizeof(float) * count);
floats += count;
weights[s_name] = tensor;
}
} catch(const std::exception &e) {}
return weights;
}
+ (NSDictionary<NSString *, NSString *> *)predict:(NSString *)buttonText
viewTree:(NSMutableDictionary *)viewTree
withLog:(BOOL)isPrint
{
if (buttonText.length == 0 || _weights.size() == 0) {
return DEFAULT_PREDICTION;
}
try {
// Get bytes tensor
NSString *textFeature = [FBSDKModelUtility normalizeText:[FBSDKFeatureExtractor getTextFeature:buttonText withScreenName:viewTree[@"screenname"]]];
if (textFeature.length == 0) {
return DEFAULT_PREDICTION;
}
const char *bytes = [textFeature UTF8String];
if ((int)strlen(bytes) == 0) {
return DEFAULT_PREDICTION;
}
// Get dense tensor
std::vector<int64_t> dense_tensor_shape;
dense_tensor_shape.push_back(1);
dense_tensor_shape.push_back(30);
mat::MTensor dense_tensor = mat::mempty(dense_tensor_shape);
float *dense_tensor_data = dense_tensor.data<float>();
float *dense_data = [FBSDKFeatureExtractor getDenseFeatures:viewTree];
if (!dense_data) {
return DEFAULT_PREDICTION;
}
NSMutableDictionary<NSString *, NSString *> *result = [[NSMutableDictionary alloc] init];
// Get dense feature string
NSMutableArray *denseDataArray = [NSMutableArray array];
for (int i=0; i < 30; i++) {
[denseDataArray addObject:[NSNumber numberWithFloat: dense_data[i]]];
}
[result setObject:[denseDataArray componentsJoinedByString:@","] forKey:DENSE_FEATURE_KEY];
memcpy(dense_tensor_data, dense_data, sizeof(float) * 30);
free(dense_data);
float *res = mat1::predictOnText(bytes, _weights, dense_tensor_data);
NSMutableDictionary<NSString *, id> *modelInfo = [[NSUserDefaults standardUserDefaults] objectForKey:MODEL_INFO_KEY];
if (!modelInfo) {
return DEFAULT_PREDICTION;
}
NSDictionary<NSString *, id> * suggestedEventModelInfo = [modelInfo objectForKey:SUGGEST_EVENT_KEY];
if (!suggestedEventModelInfo) {
return DEFAULT_PREDICTION;
}
NSMutableArray *thresholds = [suggestedEventModelInfo objectForKey:THRESHOLDS_KEY];
if (thresholds.count < 4) {
return DEFAULT_PREDICTION;
}
for (int i = 0; i < thresholds.count; i++){
if ((float)res[i] >= (float)[thresholds[i] floatValue]) {
[result setObject:SUGGESTED_EVENT[i] forKey:SUGGEST_EVENT_KEY];
return result;
}
}
} catch (const std::exception &e) {}
return DEFAULT_PREDICTION;
}
@end
#endif

View File

@ -0,0 +1,38 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKFeatureExtractor : NSObject
+ (void)loadRules;
+ (NSString *)getTextFeature:(NSString *)text
withScreenName:(NSString *)screenName;
+ (float *)getDenseFeatures:(NSDictionary *)viewHierarchy;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,345 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKFeatureExtractor.h"
#import "FBSDKCoreKit+Internal.h"
#import "FBSDKModelManager.h"
#define REGEX_CR_PASSWORD_FIELD @"password"
#define REGEX_CR_HAS_CONFIRM_PASSWORD_FIELD @"(?i)(confirm.*password)|(password.*(confirmation|confirm)|confirmation)"
#define REGEX_CR_HAS_LOG_IN_KEYWORDS @"(?i)(sign in)|login|signIn"
#define REGEX_CR_HAS_SIGN_ON_KEYWORDS @"(?i)(sign.*(up|now)|registration|" \
@"register|(create|apply).*(profile|account)|open.*account|" \
@"account.*(open|creation|application)|enroll|join.*now)"
#define REGEX_ADD_TO_CART_BUTTON_TEXT @"(?i)add to(\\s|\\Z)|update(\\s|\\Z)|cart"
#define REGEX_ADD_TO_CART_PAGE_TITLE @"(?i)add to(\\s|\\Z)|update(\\s|\\Z)|cart|shop|buy"
static NSDictionary *_languageInfo;
static NSDictionary *_eventInfo;
static NSDictionary *_textTypeInfo;
static NSDictionary *_rules;
void sum(float *val0, float *val1);
@implementation FBSDKFeatureExtractor
+ (void)initialize
{
_languageInfo = @{
@"ENGLISH" : @"1",
@"GERMAN" : @"2",
@"SPANISH" : @"3",
@"JAPANESE" : @"4"
};
_eventInfo = @{
@"VIEW_CONTENT" : @"0",
@"SEARCH" : @"1",
@"ADD_TO_CART" : @"2",
@"ADD_TO_WISHLIST" : @"3",
@"INITIATE_CHECKOUT" : @"4",
@"ADD_PAYMENT_INFO" : @"5",
@"PURCHASE" : @"6",
@"LEAD" : @"7",
@"COMPLETE_REGISTRATION" : @"8"
};
_textTypeInfo = @{
@"BUTTON_TEXT": @"1",
@"PAGE_TITLE": @"2",
@"RESOLVED_DOCUMENT_LINK": @"3",
@"BUTTON_ID": @"4"
};
}
+ (void)loadRules
{
_rules = [FBSDKModelManager getRules];
}
+ (NSString *)getTextFeature:(NSString *)text
withScreenName:(NSString *)screenName
{
// use "|" and "," to separate different text based on the rule of how text processed during training
NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleNameKey];
return [[NSString stringWithFormat:@"%@ | %@, %@", appName, screenName, text] lowercaseString];
}
+ (float *)getDenseFeatures:(NSDictionary *)viewHierarchy
{
if (!_rules) {
return nil;
}
NSMutableArray<NSMutableDictionary<NSString *, id> *> *viewTree = [viewHierarchy[VIEW_HIERARCHY_VIEW_KEY] mutableCopy];
NSString *screenName = viewHierarchy[VIEW_HIERARCHY_SCREEN_NAME_KEY];
NSMutableArray<NSMutableDictionary<NSString *, id> *> *siblings = [NSMutableArray array];
[self pruneTree:[viewTree[0] mutableCopy] siblings:siblings];
float *result = [self parseFeatures:viewTree[0]];
NSMutableDictionary<NSString *, id> *interactedNode;
for (NSMutableDictionary<NSString *, id> *node in siblings) {
if ([[node objectForKey:VIEW_HIERARCHY_IS_INTERACTED_KEY] boolValue]) {
interactedNode = node;
}
}
NSString *viewTreeString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:viewTree options:0 error:nil] encoding:NSUTF8StringEncoding];
float *nonparseResult = [self nonparseFeatures:interactedNode siblings:siblings screenname:screenName viewTreeString:viewTreeString];
sum(result, nonparseResult);
free(nonparseResult);
return result;
}
#pragma mark - Helper functions
+ (BOOL)pruneTree:(NSMutableDictionary *)node siblings:(NSMutableArray *)siblings
{
// If it's interacted, don't prune away the children and just return.
BOOL isInteracted = [[node objectForKey:VIEW_HIERARCHY_IS_INTERACTED_KEY] boolValue];
if (isInteracted) {
return true;
}
NSMutableArray<NSMutableDictionary<NSString *, id> *> *newChildren = [NSMutableArray array];
// If a child is interacted, we're at the right level and we want to grab everything
BOOL isChildInteracted = NO;
BOOL isDescendantInteracted = NO;
NSMutableArray<NSMutableDictionary<NSString *, id> *> *childviews = [node objectForKey:VIEW_HIERARCHY_CHILD_VIEWS_KEY];
for (NSMutableDictionary<NSString *, id> *child in childviews) {
if ([child[VIEW_HIERARCHY_IS_INTERACTED_KEY] boolValue]) {
isChildInteracted = YES;
isDescendantInteracted = YES;
}
}
if (isChildInteracted) {
[siblings addObjectsFromArray:childviews];
} else {
for (NSMutableDictionary<NSString *, id> *c in childviews) {
NSMutableDictionary<NSString *, id> *child = [c mutableCopy];
if ([self pruneTree:child siblings:siblings]) {
isDescendantInteracted = YES;
[newChildren addObject:child];
}
}
node[VIEW_HIERARCHY_CHILD_VIEWS_KEY] = newChildren;
}
return isDescendantInteracted;
}
+ (float *)nonparseFeatures:(NSMutableDictionary *)node
siblings:(NSMutableArray *)siblings
screenname:(NSString *)screenname
viewTreeString:(NSString *)viewTreeString
{
float *densefeat = (float *)calloc(30, sizeof(float));
densefeat[3] = MAX((float)siblings.count - 1, 0);
densefeat[9] = [siblings filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return [self isButton:evaluatedObject];
}]].count;
if ([self isButton:node]) {
densefeat[9] -= 1;
}
densefeat[13] = -1;
densefeat[14] = -1;
NSString *pageTitle = screenname ?: @"";
NSString *formFieldsJSON = viewTreeString;
NSString *buttonID = @"";
NSString *buttonText = @"";
if ([self isButton:node]) {
NSMutableString *buttonTextString = [NSMutableString string];
NSMutableString *buttonHintString = [NSMutableString string];
[self update:node text:buttonTextString hint:buttonHintString];
buttonText = (NSString *)buttonTextString;
buttonID = (NSString *)buttonHintString;
}
// Regex features
densefeat[15] = [self regexMatch:@"ENGLISH" event:@"COMPLETE_REGISTRATION" textType:@"BUTTON_TEXT" matchText:buttonText];
densefeat[16] = [self regexMatch:@"ENGLISH" event:@"COMPLETE_REGISTRATION" textType:@"PAGE_TITLE" matchText:pageTitle];
densefeat[17] = [self regexMatch:@"ENGLISH" event:@"COMPLETE_REGISTRATION" textType:@"BUTTON_ID" matchText:buttonID];
densefeat[18] = [formFieldsJSON containsString:REGEX_CR_PASSWORD_FIELD] ? 1.0 : 0.0;
densefeat[19] = [self regextMatch:REGEX_CR_HAS_CONFIRM_PASSWORD_FIELD text:formFieldsJSON];
densefeat[20] = [self regextMatch:REGEX_CR_HAS_LOG_IN_KEYWORDS text:formFieldsJSON];
densefeat[21] = [self regextMatch:REGEX_CR_HAS_SIGN_ON_KEYWORDS text:formFieldsJSON];
// Purchase specific features
densefeat[22] = [self regexMatch:@"ENGLISH" event:@"PURCHASE" textType:@"BUTTON_TEXT" matchText:buttonText];
densefeat[24] = [self regexMatch:@"ENGLISH" event:@"PURCHASE" textType:@"PAGE_TITLE" matchText:pageTitle];
// AddToCart specific features
densefeat[25] = [self regextMatch:REGEX_ADD_TO_CART_BUTTON_TEXT text:buttonText];
densefeat[27] = [self regextMatch:REGEX_ADD_TO_CART_PAGE_TITLE text:pageTitle];
// Lead specific features
densefeat[28] = [self regexMatch:@"ENGLISH" event:@"LEAD" textType:@"BUTTON_TEXT" matchText:buttonText];
densefeat[29] = [self regexMatch:@"ENGLISH" event:@"LEAD" textType:@"PAGE_TITLE" matchText:pageTitle];
return densefeat;
}
+ (float *)parseFeatures:(NSMutableDictionary *)node
{
float *densefeat = (float *)calloc(30, sizeof(float));
NSString *text = [node[VIEW_HIERARCHY_TEXT_KEY] lowercaseString] ?: @"";
NSString *hint = [node[VIEW_HIERARCHY_HINT_KEY] lowercaseString] ?: @"";
NSString *className = [node[VIEW_HIERARCHY_CLASS_NAME_KEY] lowercaseString] ?: @"";
if ([self foundIndicators:[@"$,amount,price,total" componentsSeparatedByString:@","]
inValues:@[text, hint]]) {
densefeat[0] += 1.0;
}
if ([self foundIndicators:[@"password,pwd" componentsSeparatedByString:@","]
inValues:@[text, hint]]) {
densefeat[1] += 1.0;
}
if ([self foundIndicators:[@"phone,tel" componentsSeparatedByString:@","]
inValues:@[text, hint]]) {
densefeat[2] += 1.0;
}
if ([self foundIndicators:@[@"search"]
inValues:@[text, hint]]) {
densefeat[4] += 1.0;
}
// Input field with general text
if ([className containsString:@"text"] && [className containsString:@"edit"]) {
densefeat[5] += 1.0;
}
// Input field with number or phone
if (([className containsString:@"num"] || [className containsString:@"phone"]) && [className containsString:@"edit"]) {
densefeat[6] += 1.0;
}
if ([hint containsString:@"email"] || [text containsString:@"@"]) {
densefeat[7] += 1.0;
}
// Check Box
if ([className containsString:@"checkbox"]) {
densefeat[8] += 1.0;
}
if ([self foundIndicators:[@"complete,confirm,done,submit" componentsSeparatedByString:@","]
inValues:@[text]]) {
densefeat[10] += 1.0;
}
densefeat[11] = 0.0;
// Radio Button
if ([className containsString:@"radio"] && [className containsString:@"button"]) {
densefeat[12] += 1.0;
}
NSMutableArray<NSMutableDictionary<NSString *, id> *> *childviews = node[VIEW_HIERARCHY_CHILD_VIEWS_KEY];
for (int i = 0; i < childviews.count; i++) {
sum(densefeat, [self parseFeatures:childviews[i]]);
}
return densefeat;
}
void sum(float *val0, float *val1) {
for (int i = 0; i < 30; i++) {
val0[i] += val1[i];
}
}
+ (BOOL)isButton:(NSDictionary *)node
{
int classtypebitmask = [[node objectForKey:VIEW_HIERARCHY_CLASS_TYPE_BITMASK_KEY] intValue];
return (classtypebitmask & FBCodelessClassBitmaskUIButton) > 0;
}
+ (void)update:(NSDictionary *)node
text:(NSMutableString *)buttonTextString
hint:(NSMutableString *)buttonHintString
{
NSString *text = [node[VIEW_HIERARCHY_TEXT_KEY] lowercaseString];
NSString *hint = [node[VIEW_HIERARCHY_HINT_KEY] lowercaseString];
if (text.length > 0) {
[buttonTextString appendFormat:@"%@ ", text];
}
if (hint.length > 0) {
[buttonHintString appendFormat:@"%@ ", hint];
}
NSMutableArray<NSMutableDictionary<NSString *, id> *> *childviews = node[VIEW_HIERARCHY_CHILD_VIEWS_KEY];
for (NSMutableDictionary<NSString *, id> *child in childviews) {
[self update:child text:buttonTextString hint:buttonHintString];
}
}
+ (BOOL)foundIndicators:(NSArray *)indicators inValues:(NSArray *)values
{
for (NSString *indicator in indicators) {
for (NSString *value in values) {
if ([value containsString:indicator]) {
return YES;
}
}
}
return NO;
}
+ (float)regextMatch:(NSString *)pattern text:(NSString *)text
{
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
NSRange range = NSMakeRange(0, text.length);
NSArray<NSTextCheckingResult *> *matched = [re matchesInString:text options:0 range:range];
return matched.count > 0 ? 1.0 : 0.0;
}
+ (float)regexMatch:(NSString *)language
event:(NSString *)event
textType:(NSString *)textType
matchText:(NSString *)matchText
{
NSString *pattern = _rules[@"rulesForLanguage"][_languageInfo[language]]
[@"rulesForEvent"][_eventInfo[event]]
[@"positiveRules"][_textTypeInfo[textType]];
return [self regextMatch:pattern text:matchText];
}
@end
#endif

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKSuggestedEventsIndexer : NSObject
+ (void)enable;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,271 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKSuggestedEventsIndexer.h"
#import <objc/runtime.h>
#import <sys/sysctl.h>
#import <sys/utsname.h>
#import <UIKit/UIKit.h>
#import "FBSDKCoreKit+Internal.h"
#import "FBSDKEventInferencer.h"
NSString * const OptInEvents = @"production_events";
NSString * const UnconfirmedEvents = @"eligible_for_prediction_events";
static NSMutableArray<NSMutableDictionary<NSString *, id> *> *_viewTrees;
static NSMutableSet<NSString *> *_optInEvents;
static NSMutableSet<NSString *> *_unconfirmedEvents;
@implementation FBSDKSuggestedEventsIndexer
+ (void)initialize
{
_viewTrees = [NSMutableArray array];
_optInEvents = [NSMutableSet set];
_unconfirmedEvents = [NSMutableSet set];
}
+ (void)enable
{
[FBSDKServerConfigurationManager loadServerConfigurationWithCompletionBlock:^(FBSDKServerConfiguration *serverConfiguration, NSError *error) {
if (error) {
return;
}
NSDictionary<NSString *, id> *suggestedEventsSetting = serverConfiguration.suggestedEventsSetting;
if ([suggestedEventsSetting isKindOfClass:[NSNull class]] || !suggestedEventsSetting[OptInEvents] || !suggestedEventsSetting[UnconfirmedEvents]) {
return;
}
[_optInEvents addObjectsFromArray:suggestedEventsSetting[OptInEvents]];
[_unconfirmedEvents addObjectsFromArray:suggestedEventsSetting[UnconfirmedEvents]];
[FBSDKSuggestedEventsIndexer setup];
}];
}
+ (void)setup
{
// won't do the model prediction when there is no opt-in event and unconfirmed event
if (_optInEvents.count == 0 && _unconfirmedEvents.count == 0) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// swizzle UIButton
[FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UIButton class] withBlock:^(UIButton *button) {
if (button.window) {
[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchDown];
}
} named:@"suggested_events"];
// UITableView
void (^tableViewBlock)(UITableView *tableView,
SEL cmd,
id<UITableViewDelegate> delegate) =
^(UITableView *tableView, SEL cmd, id<UITableViewDelegate> delegate) {
[self handleView:tableView withDelegate:delegate];
};
[FBSDKSwizzler swizzleSelector:@selector(setDelegate:)
onClass:[UITableView class]
withBlock:tableViewBlock
named:@"suggested_events"];
// UICollectionView
void (^collectionViewBlock)(UICollectionView *collectionView,
SEL cmd,
id<UICollectionViewDelegate> delegate) =
^(UICollectionView *collectionView, SEL cmd, id<UICollectionViewDelegate> delegate) {
[self handleView:collectionView withDelegate:delegate];
};
[FBSDKSwizzler swizzleSelector:@selector(setDelegate:)
onClass:[UICollectionView class]
withBlock:collectionViewBlock
named:@"suggested_events"];
fb_dispatch_on_main_thread(^{
[self rematchBindings];
});
});
}
+ (void)rematchBindings {
NSArray *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows) {
[self matchSubviewsIn:window];
}
}
+ (void)matchSubviewsIn:(UIView *)view {
if (!view) {
return;
}
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)subview;
[self handleView:tableView withDelegate:tableView.delegate];
} else if ([subview isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)subview;
[self handleView:collectionView withDelegate:collectionView.delegate];
} else if ([subview isKindOfClass:[UIButton class]]) {
[(UIButton *)subview addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchDown];
}
if (![subview isKindOfClass:[UIControl class]]) {
[self matchSubviewsIn:subview];
}
}
}
+ (void)buttonClicked:(UIButton *)button
{
[self predictEvent:button withText:[FBSDKViewHierarchy getText:button]];
}
+ (void)handleView:(UIView *)view withDelegate:(id)delegate
{
if (!delegate) {
return;
}
if ([view isKindOfClass:[UITableView class]]
&& [delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
void (^block)(id, SEL, id, id) = ^(id target, SEL command, UITableView *tableView, NSIndexPath *indexPath) {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self predictEvent:cell withText:[self getTextFromContentView:[cell contentView]]];
};
[FBSDKSwizzler swizzleSelector:@selector(tableView:didSelectRowAtIndexPath:)
onClass:[delegate class]
withBlock:block
named:@"suggested_events"];
} else if ([view isKindOfClass:[UICollectionView class]]
&& [delegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) {
void (^block)(id, SEL, id, id) = ^(id target, SEL command, UICollectionView *collectionView, NSIndexPath *indexPath) {
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[self predictEvent:cell withText:[self getTextFromContentView:[cell contentView]]];
};
[FBSDKSwizzler swizzleSelector:@selector(collectionView:didSelectItemAtIndexPath:)
onClass:[delegate class]
withBlock:block
named:@"suggested_events"];
}
}
+ (void)predictEvent:(NSObject *)obj withText:(NSString *)text
{
if (text.length > 100 || text.length == 0 || [FBSDKAppEventsUtility isSensitiveUserData: text]) {
return;
}
NSMutableArray<NSDictionary<NSString *, id> *> *trees = [NSMutableArray array];
fb_dispatch_on_main_thread(^{
NSArray<UIWindow *> *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows) {
NSDictionary<NSString *, id> *tree = [FBSDKViewHierarchy recursiveCaptureTree:window withObject:obj];
if (tree) {
if (window.isKeyWindow) {
[trees insertObject:tree atIndex:0];
} else {
[trees addObject:tree];
}
}
}
NSMutableDictionary<NSString *, id> *treeInfo = [NSMutableDictionary dictionary];
NSString *screenName = nil;
UIViewController *topMostViewController = [FBSDKInternalUtility topMostViewController];
if (topMostViewController) {
screenName = NSStringFromClass([topMostViewController class]);
}
treeInfo[VIEW_HIERARCHY_VIEW_KEY] = trees;
treeInfo[VIEW_HIERARCHY_SCREEN_NAME_KEY] = screenName ?: @"";
[_viewTrees addObject:treeInfo];
NSDictionary<NSString *, id> *viewTree = [_viewTrees lastObject];
fb_dispatch_on_default_thread(^{
NSDictionary<NSString *, NSString *> *result = [FBSDKEventInferencer predict:text viewTree:[viewTree mutableCopy] withLog:YES];
NSString *event = result[SUGGEST_EVENT_KEY];
if (!event || [event isEqualToString:SUGGESTED_EVENTS_OTHER]) {
return;
}
if ([_optInEvents containsObject:event]) {
[FBSDKAppEvents logEvent:event
parameters:@{@"_is_suggested_event": @"1",
@"_button_text": text
}];
} else if ([_unconfirmedEvents containsObject:event]) {
// Only send back not confirmed events to advertisers
[self logSuggestedEvent:event withText:text withDenseFeature:result[DENSE_FEATURE_KEY] ?: @""];
}
});
});
}
#pragma mark - Helper Methods
+ (NSString *)getTextFromContentView:(UIView *)contentView
{
NSMutableArray<NSString *> *textArray = [NSMutableArray array];
for (UIView *subView in [contentView subviews]) {
NSString *label = [FBSDKViewHierarchy getText:subView];
if (label.length > 0) {
[textArray addObject:label];
}
}
return [textArray componentsJoinedByString:@" "];
}
+ (void)logSuggestedEvent:(NSString *)event withText:(NSString *)text withDenseFeature:(NSString *)denseFeature
{
NSString *metadata = [FBSDKBasicUtility JSONStringForObject:@{@"button_text": text,
@"dense": denseFeature,
}
error:nil
invalidObjectHandler:nil];
if (!metadata) {
return;
}
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc]
initWithGraphPath:[NSString stringWithFormat:@"%@/suggested_events", [FBSDKSettings appID]]
parameters: @{@"event_name": event,
@"metadata": metadata,
}
HTTPMethod:FBSDKHTTPMethodPOST];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {}];
return;
}
@end
#endif

View File

@ -0,0 +1,74 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, FBCodelessClassBitmask) {
/** Indicates that the class is subclass of UIControl */
FBCodelessClassBitmaskUIControl = 1 << 3,
/** Indicates that the class is subclass of UIControl */
FBCodelessClassBitmaskUIButton = 1 << 4,
/** Indicates that the class is ReactNative Button */
FBCodelessClassBitmaskReactNativeButton = 1 << 6,
/** Indicates that the class is UITableViewCell */
FBCodelessClassBitmaskUITableViewCell = 1 << 7,
/** Indicates that the class is UICollectionViewCell */
FBCodelessClassBitmaskUICollectionViewCell = 1 << 8,
/** Indicates that the class is UILabel */
FBCodelessClassBitmaskLabel = 1 << 10,
/** Indicates that the class is UITextView or UITextField*/
FBCodelessClassBitmaskInput = 1 << 11,
/** Indicates that the class is UIPicker*/
FBCodelessClassBitmaskPicker = 1 << 12,
/** Indicates that the class is UISwitch*/
FBCodelessClassBitmaskSwitch = 1 << 13,
/** Indicates that the class is UIViewController*/
FBCodelessClassBitmaskUIViewController = 1 << 17,
};
extern void fb_dispatch_on_main_thread(dispatch_block_t block);
extern void fb_dispatch_on_default_thread(dispatch_block_t block);
NS_SWIFT_NAME(ViewHierarchy)
@interface FBSDKViewHierarchy : NSObject
+ (NSObject *)getParent:(NSObject *)obj;
+ (NSArray<NSObject *> *)getChildren:(NSObject *)obj;
+ (NSArray<NSObject *> *)getPath:(NSObject *)obj;
+ (NSMutableDictionary<NSString *, id> *)getDetailAttributesOf:(NSObject *)obj;
+ (NSString *)getText:(NSObject *)obj;
+ (NSString *)getHint:(NSObject *)obj;
+ (NSIndexPath *)getIndexPath:(NSObject *)obj;
+ (NSUInteger)getClassBitmask:(NSObject *)obj;
+ (UITableView *)getParentTableView:(UIView *)cell;
+ (UICollectionView *)getParentCollectionView:(UIView *)cell;
+ (NSInteger)getTag:(NSObject *)obj;
+ (NSNumber *)getViewReactTag:(UIView *)view;
+ (NSDictionary<NSString *, id> *)recursiveCaptureTree:(NSObject *)obj withObject:(NSObject *)interact;
+ (BOOL)isUserInputView:(NSObject *)obj;
@end
#endif

View File

@ -0,0 +1,662 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKViewHierarchy.h"
#import <objc/runtime.h>
#import <QuartzCore/QuartzCore.h>
#import "FBSDKCodelessPathComponent.h"
#import "FBSDKCoreKit+Internal.h"
#import "FBSDKViewHierarchyMacros.h"
#define MAX_VIEW_HIERARCHY_LEVEL 35
void fb_dispatch_on_main_thread(dispatch_block_t block) {
if (block != nil) {
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
}
void fb_dispatch_on_default_thread(dispatch_block_t block) {
if (block != nil) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
}
}
@implementation FBSDKViewHierarchy
+ (NSArray*)getChildren:(NSObject*)obj
{
if ([obj isKindOfClass:[UIControl class]]) {
return nil;
}
NSMutableArray *children = [NSMutableArray array];
// children of window should be viewcontroller
if ([obj isKindOfClass:[UIWindow class]]) {
UIViewController *rootVC = ((UIWindow *)obj).rootViewController;
NSArray<UIView *> *subviews = ((UIWindow *)obj).subviews;
for (UIView *child in subviews) {
if (child != rootVC.view) {
UIViewController *vc = [FBSDKViewHierarchy getParentViewController:child];
if (vc != nil && vc.view == child) {
[children addObject:vc];
} else {
[children addObject:child];
}
} else {
if (rootVC) {
[children addObject:rootVC];
}
}
}
} else if ([obj isKindOfClass:[UIView class]]) {
NSArray<UIView *> *subviews = [((UIView *)obj).subviews copy];
for (UIView *child in subviews) {
UIViewController *vc = [FBSDKViewHierarchy getParentViewController:child];
if (vc && vc.view == child) {
[children addObject:vc];
} else {
[children addObject:child];
}
}
} else if ([obj isKindOfClass:[UINavigationController class]]) {
UIViewController *vc = ((UINavigationController*)obj).visibleViewController;
UIViewController *tc = ((UINavigationController*)obj).topViewController;
NSArray *nextChildren = [FBSDKViewHierarchy getChildren:((UIViewController*)obj).view];
for (NSObject *child in nextChildren) {
if (tc && [self isView:child superViewOfView:tc.view]) {
[children addObject:tc];
} else if (vc && [self isView:child superViewOfView:vc.view]) {
[children addObject:vc];
} else {
if (child != vc.view && child != tc.view) {
[children addObject:child];
} else {
if (vc && child == vc.view) {
[children addObject:vc];
} else if (tc && child == tc.view) {
[children addObject:tc];
}
}
}
}
if (vc && ![children containsObject:vc]) {
[children addObject:vc];
}
} else if ([obj isKindOfClass:[UITabBarController class]]) {
UIViewController *vc = ((UITabBarController *)obj).selectedViewController;
NSArray *nextChildren = [FBSDKViewHierarchy getChildren:((UIViewController*)obj).view];
for (NSObject *child in nextChildren) {
if (vc && [self isView:child superViewOfView:vc.view]) {
[children addObject:vc];
} else {
if (vc && child == vc.view) {
[children addObject:vc];
} else {
[children addObject:child];
}
}
}
if (vc && ![children containsObject:vc]) {
[children addObject:vc];
}
} else if ([obj isKindOfClass:[UIViewController class]]) {
UIViewController *vc = (UIViewController *)obj;
if (vc.isViewLoaded) {
NSArray *nextChildren = [FBSDKViewHierarchy getChildren:vc.view];
if (nextChildren.count > 0) {
[children addObjectsFromArray:nextChildren];
}
}
for (NSObject *child in vc.childViewControllers) {
[children addObject:child];
}
UIViewController *presentedVC = vc.presentedViewController;
if (presentedVC) {
[children addObject:presentedVC];
}
}
return children;
}
+ (NSObject *)getParent:(NSObject *)obj
{
if ([obj isKindOfClass:[UIView class]]) {
UIView *superview = ((UIView *)obj).superview;
UIViewController *superviewViewController = [FBSDKViewHierarchy
getParentViewController:superview];
if (superviewViewController && superviewViewController.view == superview) {
return superviewViewController;
}
if (superview && superview != obj) {
return superview;
}
}
else if ([obj isKindOfClass:[UIViewController class]]) {
UIViewController *vc = (UIViewController *)obj;
UIViewController *parentVC = vc.parentViewController;
UIViewController *presentingVC = vc.presentingViewController;
UINavigationController *nav = vc.navigationController;
UITabBarController *tab = vc.tabBarController;
if (nav) {
return nav;
}
if (tab) {
return tab;
}
if (parentVC) {
return parentVC;
}
if (presentingVC && presentingVC.presentedViewController == vc) {
return presentingVC;
}
// Return parent of view of UIViewController
NSObject *viewParent = [FBSDKViewHierarchy getParent:vc.view];
if (viewParent) {
return viewParent;
}
}
return nil;
}
+ (NSArray *)getPath:(NSObject *)obj
{
return [FBSDKViewHierarchy getPath:obj limit:MAX_VIEW_HIERARCHY_LEVEL];
}
+ (NSArray *)getPath:(NSObject *)obj limit:(int)limit
{
if (!obj || limit <= 0) {
return nil;
}
NSMutableArray *path;
NSObject *parent = [FBSDKViewHierarchy getParent:obj];
if (parent) {
NSArray *parentPath = [FBSDKViewHierarchy getPath:parent limit:limit - 1];
path = [NSMutableArray arrayWithArray:parentPath];
} else {
path = [NSMutableArray array];
}
NSDictionary *componentInfo = [FBSDKViewHierarchy getAttributesOf:obj parent:parent];
FBSDKCodelessPathComponent *pathComponent = [[FBSDKCodelessPathComponent alloc]
initWithJSON:componentInfo];
[path addObject:pathComponent];
return [NSArray arrayWithArray:path];
}
+ (NSDictionary<NSString *, id> *)getAttributesOf:(NSObject *)obj parent:(NSObject *)parent
{
NSMutableDictionary *componentInfo = [NSMutableDictionary dictionary];
componentInfo[CODELESS_MAPPING_CLASS_NAME_KEY] = NSStringFromClass([obj class]);
if (![FBSDKViewHierarchy isUserInputView:obj]) {
NSString *text = [FBSDKViewHierarchy getText:obj];
if (text) {
componentInfo[CODELESS_MAPPING_TEXT_KEY] = text;
}
} else {
componentInfo[CODELESS_MAPPING_TEXT_KEY] = @"";
componentInfo[CODELESS_MAPPING_IS_USER_INPUT_KEY] = @YES;
}
NSString *hint = [FBSDKViewHierarchy getHint:obj];
if (hint) {
componentInfo[CODELESS_MAPPING_HINT_KEY] = hint;
}
NSIndexPath *indexPath = [FBSDKViewHierarchy getIndexPath:obj];
if (indexPath) {
componentInfo[CODELESS_MAPPING_SECTION_KEY] = @(indexPath.section);
componentInfo[CODELESS_MAPPING_ROW_KEY] = @(indexPath.row);
}
if (parent != nil) {
NSArray *children = [FBSDKViewHierarchy getChildren:parent];
NSUInteger index = [children indexOfObject:obj];
if (index != NSNotFound) {
componentInfo[CODELESS_MAPPING_INDEX_KEY] = @(index);
}
} else {
componentInfo[CODELESS_MAPPING_INDEX_KEY] = @0;
}
componentInfo[CODELESS_VIEW_TREE_TAG_KEY] = @([FBSDKViewHierarchy getTag:obj]);
return [componentInfo copy];
}
+ (NSMutableDictionary<NSString *, id> *)getDetailAttributesOf:(NSObject *)obj
{
return [self getDetailAttributesOf:obj withHash:YES];
}
+ (NSMutableDictionary<NSString *, id> *)getDetailAttributesOf:(NSObject *)obj withHash:(BOOL)hash
{
if (!obj) {
return nil;
}
NSObject *parent = [FBSDKViewHierarchy getParent:obj];
NSDictionary *simpleAttributes = [FBSDKViewHierarchy getAttributesOf:obj parent:parent];
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:simpleAttributes];
NSString *className = NSStringFromClass([obj class]);
result[VIEW_HIERARCHY_CLASS_NAME_KEY] = className;
NSUInteger classBitmask = [FBSDKViewHierarchy getClassBitmask:obj];
result[VIEW_HIERARCHY_CLASS_TYPE_BITMASK_KEY] = [NSString stringWithFormat:@"%lu", (unsigned long)classBitmask];
if ([obj isKindOfClass:[UIControl class]]) {
// Get actions of UIControl
UIControl *control = (UIControl *)obj;
NSMutableSet *actions = [NSMutableSet set];
NSSet *targets = control.allTargets;
for (NSObject *target in targets) {
NSArray *ary = [control actionsForTarget:target forControlEvent:0];
if (ary.count > 0) {
[actions addObjectsFromArray:ary];
}
}
if (targets.count > 0) {
result[CODELESS_VIEW_TREE_ACTIONS_KEY] = actions.allObjects;
}
}
result[CODELESS_VIEW_TREE_DIMENSION_KEY] = [FBSDKViewHierarchy getDimensionOf:obj];
NSDictionary<NSString *, id> *textStyle = [FBSDKViewHierarchy getTextStyle:obj];
if (textStyle) {
result[CODELESS_VIEW_TREE_TEXT_STYLE_KEY] = textStyle;
}
if (hash) {
// hash text and hint
result[VIEW_HIERARCHY_TEXT_KEY] = [FBSDKUtility SHA256Hash:result[VIEW_HIERARCHY_TEXT_KEY]];
result[VIEW_HIERARCHY_HINT_KEY] = [FBSDKUtility SHA256Hash:result[VIEW_HIERARCHY_HINT_KEY]];
}
return result;
}
+ (NSIndexPath *)getIndexPath:(NSObject *)obj
{
NSIndexPath *indexPath = nil;
if ([obj isKindOfClass:[UITableViewCell class]]) {
UITableView *tableView = [FBSDKViewHierarchy getParentTableView:(UIView *)obj];
indexPath = [tableView indexPathForCell:(UITableViewCell *)obj];
} else if ([obj isKindOfClass:[UICollectionViewCell class]]) {
UICollectionView *collectionView = [FBSDKViewHierarchy getParentCollectionView:(UIView *)obj];
indexPath = [collectionView indexPathForCell:(UICollectionViewCell *)obj];
}
return indexPath;
}
+ (NSString *)getText:(NSObject *)obj
{
NSString *text = nil;
if ([obj isKindOfClass:[UIButton class]]) {
text = ((UIButton *)obj).currentTitle;
} else if ([obj isKindOfClass:[UITextView class]] ||
[obj isKindOfClass:[UITextField class]] ||
[obj isKindOfClass:[UILabel class]]) {
text = ((UILabel *)obj).text;
} else if ([obj isKindOfClass:[UIPickerView class]]) {
UIPickerView *picker = (UIPickerView *)obj;
NSInteger sections = picker.numberOfComponents;
NSMutableArray *titles = [NSMutableArray array];
for (NSInteger i = 0; i < sections; i++) {
NSInteger row = [picker selectedRowInComponent:i];
NSString *title;
if ([picker.delegate
respondsToSelector:@selector(pickerView:titleForRow:forComponent:)]) {
title = [picker.delegate pickerView:picker titleForRow:row forComponent:i];
} else if ([picker.delegate
respondsToSelector:@selector(pickerView:attributedTitleForRow:forComponent:)]) {
title = [picker.delegate
pickerView:picker
attributedTitleForRow:row forComponent:i].string;
}
[titles addObject:title ?: @""];
}
if (titles.count > 0) {
text = [FBSDKBasicUtility JSONStringForObject:titles
error:NULL
invalidObjectHandler:NULL];
}
} else if ([obj isKindOfClass:[UIDatePicker class]]) {
UIDatePicker *picker = (UIDatePicker *)obj;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ssZ";
text = [formatter stringFromDate:picker.date];
} else if ([obj isKindOfClass:objc_lookUpClass("RCTTextView")]) {
NSTextStorage *textStorage = [FBSDKAppEventsUtility getVariable:@"_textStorage"
fromInstance:obj];
if (textStorage) {
text = textStorage.string;
}
} else if ([obj isKindOfClass:objc_lookUpClass("RCTBaseTextInputView")]) {
NSAttributedString *attributedText = [FBSDKAppEventsUtility getVariable:@"attributedText"
fromInstance:obj];
text = attributedText.string;
}
return text.length > 0 ? text : nil;
}
+ (NSDictionary<NSString *, id> *)getTextStyle:(NSObject *)obj
{
UIFont *font = nil;
if ([obj isKindOfClass:[UIButton class]]) {
font = ((UIButton *)obj).titleLabel.font;
} else if ([obj isKindOfClass:[UILabel class]]) {
font = ((UILabel *)obj).font;
} else if ([obj isKindOfClass:[UITextField class]]) {
font = ((UITextField *)obj).font;
} else if ([obj isKindOfClass:[UITextView class]]) {
font = ((UITextView *)obj).font;
}
if (font) {
UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
BOOL isBold = (traits & UIFontDescriptorTraitBold) != 0;
BOOL isItalic = (traits & UIFontDescriptorTraitItalic) != 0;
CGFloat fontSize = font.pointSize;
return @{
CODELESS_VIEW_TREE_TEXT_IS_BOLD_KEY: @(isBold),
CODELESS_VIEW_TREE_TEXT_IS_ITALIC_KEY: @(isItalic),
CODELESS_VIEW_TREE_TEXT_SIZE_KEY: @(fontSize)
};
}
return nil;
}
+ (NSString *)getHint:(NSObject *)obj
{
NSString *hint = nil;
if ([obj isKindOfClass:[UITextField class]]) {
hint = ((UITextField *)obj).placeholder;
} else if ([obj isKindOfClass:[UINavigationController class]]) {
UIViewController *top = ((UINavigationController *)obj).topViewController;
if (top) {
hint = NSStringFromClass([top class]);
}
}
return hint.length > 0 ? hint : nil;
}
+ (NSUInteger)getClassBitmask:(NSObject *)obj
{
NSUInteger bitmask = 0;
if ([obj isKindOfClass:[UIView class]]) {
if ([obj isKindOfClass:[UIControl class]]) {
bitmask |= FBCodelessClassBitmaskUIControl;
if ([obj isKindOfClass:[UIButton class]]) {
bitmask |= FBCodelessClassBitmaskUIButton;
} else if ([obj isKindOfClass:[UISwitch class]]) {
bitmask |= FBCodelessClassBitmaskSwitch;
}else if ([obj isKindOfClass:[UIDatePicker class]]) {
bitmask |= FBCodelessClassBitmaskPicker;
}
} else if ([obj isKindOfClass:[UITableViewCell class]]) {
bitmask |= FBCodelessClassBitmaskUITableViewCell;
} else if ([obj isKindOfClass:[UICollectionViewCell class]]) {
bitmask |= FBCodelessClassBitmaskUICollectionViewCell;
} else if ([obj isKindOfClass:[UIPickerView class]]) {
bitmask |= FBCodelessClassBitmaskPicker;
} else if ([obj isKindOfClass:[UILabel class]]) {
bitmask |= FBCodelessClassBitmaskLabel;
}
if ([FBSDKViewHierarchy isRCTButton:((UIView *)obj)]) {
bitmask |= FBCodelessClassBitmaskReactNativeButton;
}
// Check selector of UITextInput protocol instead of checking conformsToProtocol
if ([obj respondsToSelector:@selector(textInRange:)]) {
bitmask |= FBCodelessClassBitmaskInput;
}
} else if ([obj isKindOfClass:[UIViewController class]]) {
bitmask |= FBCodelessClassBitmaskUIViewController;
}
return bitmask;
}
+ (BOOL)isUserInputView:(NSObject *)obj
{
if (obj && [obj conformsToProtocol:@protocol(UITextInput)]) {
id<UITextInput> input = (id<UITextInput>)obj;
if ([input respondsToSelector:@selector(isSecureTextEntry)]
&& input.secureTextEntry) {
return YES;
} else {
if ([input respondsToSelector:@selector(keyboardType)]) {
switch (input.keyboardType) {
case UIKeyboardTypePhonePad:
case UIKeyboardTypeEmailAddress:
return YES;
default: break;
}
}
}
}
NSString *text = [FBSDKViewHierarchy getText:obj];
return text && [FBSDKAppEventsUtility isSensitiveUserData:text];
}
+ (NSDictionary<NSString *, id> *)recursiveCaptureTree:(NSObject *)obj withObject:(NSObject *)interact
{
if (!obj) {
return nil;
}
NSMutableDictionary<NSString *, id> *result = [FBSDKViewHierarchy getDetailAttributesOf:obj withHash:NO];
NSArray<NSObject *> *children = [FBSDKViewHierarchy getChildren:obj];
NSMutableArray<NSDictionary<NSString *, id> *> *childrenTrees = [NSMutableArray array];
for (NSObject *child in children) {
NSDictionary<NSString *, id> *objTree = [self recursiveCaptureTree:child withObject:interact];
[childrenTrees addObject:objTree];
}
if (childrenTrees.count > 0) {
[result setObject:[childrenTrees copy] forKey:VIEW_HIERARCHY_CHILD_VIEWS_KEY];
}
if (obj == interact) {
[result setObject:[NSNumber numberWithBool:YES] forKey:VIEW_HIERARCHY_IS_INTERACTED_KEY];
}
return [result copy];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (BOOL)isRCTButton:(UIView *)view
{
if (view == nil) {
return NO;
}
Class classRCTView = objc_lookUpClass(ReactNativeClassRCTView);
if (classRCTView && [view isKindOfClass:classRCTView] &&
[view respondsToSelector:@selector(reactTagAtPoint:)] &&
[view respondsToSelector:@selector(reactTag)] &&
view.userInteractionEnabled) {
// We check all its subviews locations and the view is clickable if there exists one that mathces reactTagAtPoint
for (UIView *subview in view.subviews) {
if (subview && ![subview isKindOfClass:classRCTView]) {
NSNumber *reactTag = [view performSelector:@selector(reactTagAtPoint:)
withObject:[NSValue valueWithCGPoint:subview.frame.origin]];
NSNumber *subviewReactTag = [FBSDKViewHierarchy getViewReactTag:subview];
if (reactTag != nil && subviewReactTag != nil && [reactTag isEqualToNumber:subviewReactTag]) {
return YES;
}
}
}
}
return NO;
}
+ (NSNumber *)getViewReactTag:(UIView *)view
{
if (view != nil && [view respondsToSelector:@selector(reactTag)]) {
NSNumber *reactTag = [view performSelector:@selector(reactTag)];
if (reactTag != nil && [reactTag isKindOfClass:[NSNumber class]]) {
return reactTag;
}
}
return nil;
}
#pragma clang diagnostic pop
+ (BOOL)isView:(NSObject *)obj1 superViewOfView:(UIView *)obj2
{
if (![obj1 isKindOfClass:[UIView class]]
|| ![obj2 isKindOfClass:[UIView class]]) {
return NO;
}
UIView *view1 = (UIView *)obj1;
UIView *view2 = (UIView *)obj2;
UIView *superview = view2;
while (superview) {
superview = superview.superview;
if (superview == view1) {
return YES;
}
}
return NO;
}
+ (UIViewController *)getParentViewController:(UIView *)view
{
UIResponder *parentResponder = view;
while (parentResponder) {
parentResponder = parentResponder.nextResponder;
if ([parentResponder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)parentResponder;
}
}
return nil;
}
+ (UITableView *)getParentTableView:(UIView *)cell
{
UIView *superview = cell.superview;
while (superview) {
if ([superview isKindOfClass:[UITableView class]]) {
return (UITableView *)superview;
}
superview = superview.superview;
}
return nil;
}
+ (UICollectionView *)getParentCollectionView:(UIView *)cell
{
UIView *superview = cell.superview;
while (superview) {
if ([superview isKindOfClass:[UICollectionView class]]) {
return (UICollectionView *)superview;
}
superview = superview.superview;
}
return nil;
}
+ (NSInteger)getTag:(NSObject *)obj
{
if ([obj isKindOfClass:[UIView class]]) {
return ((UIView *)obj).tag;
} else if ([obj isKindOfClass:[UIViewController class]]) {
return ((UIViewController *)obj).view.tag;
}
return 0;
}
+ (NSDictionary<NSString *, NSNumber *> *)getDimensionOf:(NSObject *)obj
{
UIView *view = nil;
if ([obj isKindOfClass:[UIView class]]) {
view = (UIView *)obj;
} else if ([obj isKindOfClass:[UIViewController class]]) {
view = ((UIViewController *)obj).view;
}
CGRect frame = view.frame;
CGPoint offset = CGPointZero;
if ([view isKindOfClass:[UIScrollView class]])
offset = ((UIScrollView *)view).contentOffset;
return @{
CODELESS_VIEW_TREE_TOP_KEY: @((int)frame.origin.y),
CODELESS_VIEW_TREE_LEFT_KEY: @((int)frame.origin.x),
CODELESS_VIEW_TREE_WIDTH_KEY: @((int)frame.size.width),
CODELESS_VIEW_TREE_HEIGHT_KEY: @((int)frame.size.height),
CODELESS_VIEW_TREE_OFFSET_X_KEY: @((int)offset.x),
CODELESS_VIEW_TREE_OFFSET_Y_KEY: @((int)offset.y),
CODELESS_VIEW_TREE_VISIBILITY_KEY: view.isHidden ? @4 : @0
};
}
@end
#endif

View File

@ -0,0 +1,102 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef FBSDKViewHierarchyMacros_h
#define FBSDKViewHierarchyMacros_h
// keys for event binding path compoenent
#define CODELESS_MAPPING_METHOD_KEY @"method"
#define CODELESS_MAPPING_EVENT_NAME_KEY @"event_name"
#define CODELESS_MAPPING_EVENT_TYPE_KEY @"event_type"
#define CODELESS_MAPPING_APP_VERSION_KEY @"app_version"
#define CODELESS_MAPPING_PATH_KEY @"path"
#define CODELESS_MAPPING_PATH_TYPE_KEY @"path_type"
#define CODELESS_MAPPING_CLASS_NAME_KEY @"class_name"
#define CODELESS_MAPPING_MATCH_BITMASK_KEY @"match_bitmask"
#define CODELESS_MAPPING_ID_KEY @"id"
#define CODELESS_MAPPING_INDEX_KEY @"index"
#define CODELESS_MAPPING_IS_USER_INPUT_KEY @"is_user_input"
#define CODELESS_MAPPING_SECTION_KEY @"section"
#define CODELESS_MAPPING_ROW_KEY @"row"
#define CODELESS_MAPPING_TEXT_KEY @"text"
#define CODELESS_MAPPING_TAG_KEY @"tag"
#define CODELESS_MAPPING_DESC_KEY @"description"
#define CODELESS_MAPPING_HINT_KEY @"hint"
#define CODELESS_MAPPING_PARAMETERS_KEY @"parameters"
#define CODELESS_MAPPING_PARAMETER_NAME_KEY @"name"
#define CODELESS_MAPPING_PARAMETER_VALUE_KEY @"value"
#define CODELESS_MAPPING_PARENT_CLASS_NAME @".."
#define CODELESS_MAPPING_CURRENT_CLASS_NAME @"."
#define ReactNativeClassRCTView "RCTView"
#define ReactNativeClassRCTRootView "RCTRootView"
#define CODELESS_INDEXING_UPLOAD_INTERVAL_IN_SECONDS 1
#define CODELESS_INDEXING_STATUS_KEY @"is_app_indexing_enabled"
#define CODELESS_INDEXING_SESSION_ID_KEY @"device_session_id"
#define CODELESS_INDEXING_APP_VERSION_KEY @"app_version"
#define CODELESS_INDEXING_SDK_VERSION_KEY @"sdk_version"
#define CODELESS_INDEXING_PLATFORM_KEY @"platform"
#define CODELESS_INDEXING_TREE_KEY @"tree"
#define CODELESS_INDEXING_SCREENSHOT_KEY @"screenshot"
#define CODELESS_INDEXING_EXT_INFO_KEY @"extinfo"
#define CODELESS_INDEXING_ENDPOINT @"app_indexing"
#define CODELESS_INDEXING_SESSION_ENDPOINT @"app_indexing_session"
#define CODELESS_SETUP_ENABLED_FIELD @"auto_event_setup_enabled"
#define CODELESS_SETUP_ENABLED_KEY @"codeless_setup_enabled"
#define CODELESS_SETTING_KEY @"com.facebook.sdk:codelessSetting%@"
#define CODELESS_SETTING_TIMESTAMP_KEY @"codeless_setting_timestamp"
#define CODELESS_SETTING_CACHE_TIMEOUT (7 * 24 * 60 * 60)
// keys for view tree
#define CODELESS_VIEW_TREE_DESC_KEY @"description"
#define CODELESS_VIEW_TREE_DIMENSION_KEY @"dimension"
#define CODELESS_VIEW_TREE_TAG_KEY @"tag"
#define CODELESS_VIEW_TREE_ACTIONS_KEY @"actions"
#define CODELESS_VIEW_TREE_TOP_KEY @"top"
#define CODELESS_VIEW_TREE_LEFT_KEY @"left"
#define CODELESS_VIEW_TREE_WIDTH_KEY @"width"
#define CODELESS_VIEW_TREE_HEIGHT_KEY @"height"
#define CODELESS_VIEW_TREE_OFFSET_X_KEY @"scrollx"
#define CODELESS_VIEW_TREE_OFFSET_Y_KEY @"scrolly"
#define CODELESS_VIEW_TREE_VISIBILITY_KEY @"visibility"
#define CODELESS_VIEW_TREE_TEXT_STYLE_KEY @"text_style"
#define CODELESS_VIEW_TREE_TEXT_IS_BOLD_KEY @"is_bold"
#define CODELESS_VIEW_TREE_TEXT_IS_ITALIC_KEY @"is_italic"
#define CODELESS_VIEW_TREE_TEXT_SIZE_KEY @"font_size"
// keys for view hierarchy
#define VIEW_HIERARCHY_CHILD_VIEWS_KEY @"childviews"
#define VIEW_HIERARCHY_CLASS_NAME_KEY @"classname"
#define VIEW_HIERARCHY_CLASS_TYPE_BITMASK_KEY @"classtypebitmask"
#define VIEW_HIERARCHY_HINT_KEY @"hint"
#define VIEW_HIERARCHY_IS_INTERACTED_KEY @"is_interacted"
#define VIEW_HIERARCHY_SCREEN_NAME_KEY @"screenname"
#define VIEW_HIERARCHY_TEXT_KEY @"text"
#define VIEW_HIERARCHY_VIEW_KEY @"view"
// keys for suggested events
#define DENSE_FEATURE_KEY @"DENSE_FEATURE"
#define SUGGEST_EVENT_KEY @"SUGGEST_EVENT"
#define SUGGESTED_EVENTS_OTHER @"other"
#endif /* FBSDKCodelessMacros_h */

View File

@ -0,0 +1,75 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "FBSDKAppLinkTarget.h"
NS_ASSUME_NONNULL_BEGIN
/** The version of the App Link protocol that this library supports */
FOUNDATION_EXPORT NSString *const FBSDKAppLinkVersion
NS_SWIFT_NAME(AppLinkVersion);
/**
Contains App Link metadata relevant for navigation on this device
derived from the HTML at a given URL.
*/
NS_SWIFT_NAME(AppLink)
@interface FBSDKAppLink : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
Creates a FBSDKAppLink with the given list of FBSDKAppLinkTargets and target URL.
Generally, this will only be used by implementers of the FBSDKAppLinkResolving protocol,
as these implementers will produce App Link metadata for a given URL.
@param sourceURL the URL from which this App Link is derived
@param targets an ordered list of FBSDKAppLinkTargets for this platform derived
from App Link metadata.
@param webURL the fallback web URL, if any, for the app link.
*/
+ (instancetype)appLinkWithSourceURL:(nullable NSURL *)sourceURL
targets:(NSArray<FBSDKAppLinkTarget *> *)targets
webURL:(nullable NSURL *)webURL
NS_SWIFT_NAME(init(sourceURL:targets:webURL:));
/** The URL from which this FBSDKAppLink was derived */
@property (nonatomic, strong, readonly, nullable) NSURL *sourceURL;
/**
The ordered list of targets applicable to this platform that will be used
for navigation.
*/
@property (nonatomic, copy, readonly) NSArray<FBSDKAppLinkTarget *> *targets;
/** The fallback web URL to use if no targets are installed on this device. */
@property (nonatomic, strong, readonly, nullable) NSURL *webURL;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,76 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLink_Internal.h"
NSString *const FBSDKAppLinkDataParameterName = @"al_applink_data";
NSString *const FBSDKAppLinkTargetKeyName = @"target_url";
NSString *const FBSDKAppLinkUserAgentKeyName = @"user_agent";
NSString *const FBSDKAppLinkExtrasKeyName = @"extras";
NSString *const FBSDKAppLinkRefererAppLink = @"referer_app_link";
NSString *const FBSDKAppLinkRefererAppName = @"app_name";
NSString *const FBSDKAppLinkRefererUrl = @"url";
NSString *const FBSDKAppLinkVersionKeyName = @"version";
NSString *const FBSDKAppLinkVersion = @"1.0";
@interface FBSDKAppLink ()
@property (nonatomic, strong) NSURL *sourceURL;
@property (nonatomic, copy) NSArray<FBSDKAppLinkTarget *> *targets;
@property (nonatomic, strong) NSURL *webURL;
@property (nonatomic, assign, getter=isBackToReferrer) BOOL backToReferrer;
@end
@implementation FBSDKAppLink
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray<FBSDKAppLinkTarget *> *)targets
webURL:(NSURL *)webURL
isBackToReferrer:(BOOL)isBackToReferrer {
FBSDKAppLink *link = [[self alloc] initWithIsBackToReferrer:isBackToReferrer];
link.sourceURL = sourceURL;
link.targets = [targets copy];
link.webURL = webURL;
return link;
}
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray<FBSDKAppLinkTarget *> *)targets
webURL:(NSURL *)webURL {
return [self appLinkWithSourceURL:sourceURL
targets:targets
webURL:webURL
isBackToReferrer:NO];
}
- (FBSDKAppLink *)initWithIsBackToReferrer:(BOOL)backToReferrer {
if ((self = [super init])) {
_backToReferrer = backToReferrer;
}
return self;
}
@end
#endif

View File

@ -0,0 +1,147 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkResolving.h"
NS_ASSUME_NONNULL_BEGIN
/**
The result of calling navigate on a FBSDKAppLinkNavigation
*/
typedef NS_ENUM(NSInteger, FBSDKAppLinkNavigationType) {
/** Indicates that the navigation failed and no app was opened */
FBSDKAppLinkNavigationTypeFailure,
/** Indicates that the navigation succeeded by opening the URL in the browser */
FBSDKAppLinkNavigationTypeBrowser,
/** Indicates that the navigation succeeded by opening the URL in an app on the device */
FBSDKAppLinkNavigationTypeApp
} NS_SWIFT_NAME(AppLinkNavigation.Type);
/**
Describes the callback for appLinkFromURLInBackground.
@param navType the FBSDKAppLink representing the deferred App Link
@param error the error during the request, if any
*/
typedef void (^FBSDKAppLinkNavigationBlock)(FBSDKAppLinkNavigationType navType, NSError * _Nullable error)
NS_SWIFT_NAME(AppLinkNavigationBlock);
/**
Represents a pending request to navigate to an App Link. Most developers will
simply use navigateToURLInBackground: to open a URL, but developers can build
custom requests with additional navigation and app data attached to them by
creating FBSDKAppLinkNavigations themselves.
*/
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension")
NS_SWIFT_NAME(AppLinkNavigation)
@interface FBSDKAppLinkNavigation : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
The default resolver to be used for App Link resolution. If the developer has not set one explicitly,
a basic, built-in FBSDKWebViewAppLinkResolver will be used.
*/
@property (class, nonatomic, strong) id<FBSDKAppLinkResolving> defaultResolver
NS_SWIFT_NAME(default);
/**
The extras for the AppLinkNavigation. This will generally contain application-specific
data that should be passed along with the request, such as advertiser or affiliate IDs or
other such metadata relevant on this device.
*/
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *extras;
/**
The al_applink_data for the AppLinkNavigation. This will generally contain data common to
navigation attempts such as back-links, user agents, and other information that may be used
in routing and handling an App Link request.
*/
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *appLinkData;
/** The AppLink to navigate to */
@property (nonatomic, strong, readonly) FBSDKAppLink *appLink;
/**
Return navigation type for current instance.
No-side-effect version of navigate:
*/
@property (nonatomic, readonly) FBSDKAppLinkNavigationType navigationType;
/** Creates an AppLinkNavigation with the given link, extras, and App Link data */
+ (instancetype)navigationWithAppLink:(FBSDKAppLink *)appLink
extras:(NSDictionary<NSString *, id> *)extras
appLinkData:(NSDictionary<NSString *, id> *)appLinkData
NS_SWIFT_NAME(init(appLink:extras:appLinkData:));
/**
Creates an NSDictionary with the correct format for iOS callback URLs,
to be used as 'appLinkData' argument in the call to navigationWithAppLink:extras:appLinkData:
*/
+ (NSDictionary<NSString *, NSDictionary<NSString *, NSString *> *> *)callbackAppLinkDataForAppWithName:(NSString *)appName
url:(NSString *)url
NS_SWIFT_NAME(callbackAppLinkData(forApp:url:));
/** Performs the navigation */
- (FBSDKAppLinkNavigationType)navigate:(NSError **)error
__attribute__((swift_error(nonnull_error)));
/** Returns a FBSDKAppLink for the given URL */
+ (void)resolveAppLink:(NSURL *)destination handler:(FBSDKAppLinkBlock)handler;
/** Returns a FBSDKAppLink for the given URL using the given App Link resolution strategy */
+ (void)resolveAppLink:(NSURL *)destination
resolver:(id<FBSDKAppLinkResolving>)resolver
handler:(FBSDKAppLinkBlock)handler;
/** Navigates to a FBSDKAppLink and returns whether it opened in-app or in-browser */
+ (FBSDKAppLinkNavigationType)navigateToAppLink:(FBSDKAppLink *)link error:(NSError **)error
__attribute__((swift_error(nonnull_error)));
/**
Returns a FBSDKAppLinkNavigationType based on a FBSDKAppLink.
It's essentially a no-side-effect version of navigateToAppLink:error:,
allowing apps to determine flow based on the link type (e.g. open an
internal web view instead of going straight to the browser for regular links.)
*/
+ (FBSDKAppLinkNavigationType)navigationTypeForLink:(FBSDKAppLink *)link;
/** Navigates to a URL (an asynchronous action) and returns a FBSDKNavigationType */
+ (void)navigateToURL:(NSURL *)destination handler:(FBSDKAppLinkNavigationBlock)handler;
/**
Navigates to a URL (an asynchronous action) using the given App Link resolution
strategy and returns a FBSDKNavigationType
*/
+ (void)navigateToURL:(NSURL *)destination
resolver:(id<FBSDKAppLinkResolving>)resolver
handler:(FBSDKAppLinkNavigationBlock)handler;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,300 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkNavigation.h"
#import "FBSDKAppLinkTarget.h"
#import "FBSDKAppLink_Internal.h"
#import "FBSDKMeasurementEvent_Internal.h"
#import "FBSDKSettings.h"
#import "FBSDKWebViewAppLinkResolver.h"
FOUNDATION_EXPORT NSString *const FBSDKAppLinkDataParameterName;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkTargetKeyName;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkUserAgentKeyName;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkExtrasKeyName;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkVersionKeyName;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkRefererAppLink;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkRefererAppName;
FOUNDATION_EXPORT NSString *const FBSDKAppLinkRefererUrl;
static id<FBSDKAppLinkResolving> defaultResolver;
@interface FBSDKAppLinkNavigation ()
@property (nonatomic, copy) NSDictionary<NSString *, id> *extras;
@property (nonatomic, copy) NSDictionary<NSString *, id> *appLinkData;
@property (nonatomic, strong) FBSDKAppLink *appLink;
@end
@implementation FBSDKAppLinkNavigation
+ (instancetype)navigationWithAppLink:(FBSDKAppLink *)appLink
extras:(NSDictionary<NSString *, id> *)extras
appLinkData:(NSDictionary<NSString *, id> *)appLinkData {
FBSDKAppLinkNavigation *navigation = [[self alloc] init];
navigation.appLink = appLink;
navigation.extras = extras;
navigation.appLinkData = appLinkData;
return navigation;
}
+ (NSDictionary<NSString *, NSDictionary<NSString *, NSString *> *> *)callbackAppLinkDataForAppWithName:(NSString *)appName
url:(NSString *)url {
return @{FBSDKAppLinkRefererAppLink: @{FBSDKAppLinkRefererAppName: appName, FBSDKAppLinkRefererUrl: url}};
}
- (NSString *)stringByEscapingQueryString:(NSString *)string {
return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
}
- (NSURL *)appLinkURLWithTargetURL:(NSURL *)targetUrl error:(NSError **)error {
NSMutableDictionary<NSString *, id> *appLinkData =
[NSMutableDictionary dictionaryWithDictionary:self.appLinkData ?: @{}];
// Add applink protocol data
if (!appLinkData[FBSDKAppLinkUserAgentKeyName]) {
appLinkData[FBSDKAppLinkUserAgentKeyName] = [NSString stringWithFormat:@"FBSDK %@", FBSDKSettings.sdkVersion];
}
if (!appLinkData[FBSDKAppLinkVersionKeyName]) {
appLinkData[FBSDKAppLinkVersionKeyName] = FBSDKAppLinkVersion;
}
if (self.appLink.sourceURL.absoluteString) {
appLinkData[FBSDKAppLinkTargetKeyName] = self.appLink.sourceURL.absoluteString;
}
appLinkData[FBSDKAppLinkExtrasKeyName] = self.extras ?: @{};
// JSON-ify the applink data
NSError *jsonError = nil;
NSData *jsonBlob = [NSJSONSerialization dataWithJSONObject:appLinkData options:0 error:&jsonError];
if (!jsonError) {
NSString *jsonString = [[NSString alloc] initWithData:jsonBlob encoding:NSUTF8StringEncoding];
NSString *encoded = [self stringByEscapingQueryString:jsonString];
NSString *endUrlString = [NSString stringWithFormat:@"%@%@%@=%@",
targetUrl.absoluteString,
targetUrl.query ? @"&" : @"?",
FBSDKAppLinkDataParameterName,
encoded];
return [NSURL URLWithString:endUrlString];
} else {
if (error) {
*error = jsonError;
}
// If there was an error encoding the app link data, fail hard.
return nil;
}
}
- (FBSDKAppLinkNavigationType)navigate:(NSError **)error {
NSURL *openedURL = nil;
NSError *encodingError = nil;
FBSDKAppLinkNavigationType retType = FBSDKAppLinkNavigationTypeFailure;
// Find the first eligible/launchable target in the FBSDKAppLink.
for (FBSDKAppLinkTarget *target in self.appLink.targets) {
NSURL *appLinkAppURL = [self appLinkURLWithTargetURL:target.URL error:&encodingError];
if (encodingError || !appLinkAppURL) {
if (error) {
*error = encodingError;
}
} else if ([[UIApplication sharedApplication] openURL:appLinkAppURL]) {
retType = FBSDKAppLinkNavigationTypeApp;
openedURL = appLinkAppURL;
break;
}
}
if (!openedURL && self.appLink.webURL) {
// Fall back to opening the url in the browser if available.
NSURL *appLinkBrowserURL = [self appLinkURLWithTargetURL:self.appLink.webURL error:&encodingError];
if (encodingError || !appLinkBrowserURL) {
// If there was an error encoding the app link data, fail hard.
if (error) {
*error = encodingError;
}
} else if ([[UIApplication sharedApplication] openURL:appLinkBrowserURL]) {
// This was a browser navigation.
retType = FBSDKAppLinkNavigationTypeBrowser;
openedURL = appLinkBrowserURL;
}
}
[self postAppLinkNavigateEventNotificationWithTargetURL:openedURL
error:error ? *error : nil
type:retType];
return retType;
}
- (void)postAppLinkNavigateEventNotificationWithTargetURL:(NSURL *)outputURL error:(NSError *)error type:(FBSDKAppLinkNavigationType)type {
NSString *const EVENT_YES_VAL = @"1";
NSString *const EVENT_NO_VAL = @"0";
NSMutableDictionary<NSString *, id> *logData =
[[NSMutableDictionary alloc] init];
NSString *outputURLScheme = outputURL.scheme;
NSString *outputURLString = outputURL.absoluteString;
if (outputURLScheme) {
logData[@"outputURLScheme"] = outputURLScheme;
}
if (outputURLString) {
logData[@"outputURL"] = outputURLString;
}
NSString *sourceURLString = self.appLink.sourceURL.absoluteString;
NSString *sourceURLHost = self.appLink.sourceURL.host;
NSString *sourceURLScheme = self.appLink.sourceURL.scheme;
if (sourceURLString) {
logData[@"sourceURL"] = sourceURLString;
}
if (sourceURLHost) {
logData[@"sourceHost"] = sourceURLHost;
}
if (sourceURLScheme) {
logData[@"sourceScheme"] = sourceURLScheme;
}
if (error.localizedDescription) {
logData[@"error"] = error.localizedDescription;
}
NSString *success = nil; //no
NSString *linkType = nil; // unknown;
switch (type) {
case FBSDKAppLinkNavigationTypeFailure:
success = EVENT_NO_VAL;
linkType = @"fail";
break;
case FBSDKAppLinkNavigationTypeBrowser:
success = EVENT_YES_VAL;
linkType = @"web";
break;
case FBSDKAppLinkNavigationTypeApp:
success = EVENT_YES_VAL;
linkType = @"app";
break;
default:
break;
}
if (success) {
logData[@"success"] = success;
}
if (linkType) {
logData[@"type"] = linkType;
}
if (self.appLink.backToReferrer) {
[FBSDKMeasurementEvent postNotificationForEventName:FBSDKAppLinkNavigateBackToReferrerEventName args:logData];
} else {
[FBSDKMeasurementEvent postNotificationForEventName:FBSDKAppLinkNavigateOutEventName args:logData];
}
}
+ (void)resolveAppLink:(NSURL *)destination
resolver:(id<FBSDKAppLinkResolving>)resolver
handler:(FBSDKAppLinkBlock)handler {
[resolver appLinkFromURL:destination handler:handler];
}
+ (void)resolveAppLink:(NSURL *)destination handler:(FBSDKAppLinkBlock)handler {
[self resolveAppLink:destination resolver:[self defaultResolver] handler:handler];
}
+ (void)navigateToURL:(NSURL *)destination handler:(FBSDKAppLinkNavigationBlock)handler {
[self navigateToURL:destination resolver:[self defaultResolver] handler:handler];
}
+ (void)navigateToURL:(NSURL *)destination
resolver:(id<FBSDKAppLinkResolving>)resolver
handler:(FBSDKAppLinkNavigationBlock)handler {
dispatch_async(dispatch_get_main_queue(), ^{
[self resolveAppLink:destination
resolver:resolver
handler:^(FBSDKAppLink * _Nullable appLink, NSError * _Nullable error) {
if (error) {
handler(FBSDKAppLinkNavigationTypeFailure, error);
return;
}
NSError *navigateError = nil;
FBSDKAppLinkNavigationType result = [self navigateToAppLink:appLink error:&navigateError];
handler(result, navigateError);
}];
});
}
+ (FBSDKAppLinkNavigationType)navigateToAppLink:(FBSDKAppLink *)link error:(NSError **)error {
return [[FBSDKAppLinkNavigation navigationWithAppLink:link
extras:@{}
appLinkData:@{}] navigate:error];
}
+ (FBSDKAppLinkNavigationType)navigationTypeForLink:(FBSDKAppLink *)link {
return [[self navigationWithAppLink:link extras:@{} appLinkData:@{}] navigationType];
}
- (FBSDKAppLinkNavigationType)navigationType {
FBSDKAppLinkTarget *eligibleTarget = nil;
for (FBSDKAppLinkTarget *target in self.appLink.targets) {
if ([[UIApplication sharedApplication] canOpenURL:target.URL]) {
eligibleTarget = target;
break;
}
}
if (eligibleTarget != nil) {
NSURL *appLinkURL = [self appLinkURLWithTargetURL:eligibleTarget.URL error:nil];
if (appLinkURL != nil) {
return FBSDKAppLinkNavigationTypeApp;
} else {
return FBSDKAppLinkNavigationTypeFailure;
}
}
if (self.appLink.webURL != nil) {
NSURL *appLinkURL = [self appLinkURLWithTargetURL:eligibleTarget.URL error:nil];
if (appLinkURL != nil) {
return FBSDKAppLinkNavigationTypeBrowser;
} else {
return FBSDKAppLinkNavigationTypeFailure;
}
}
return FBSDKAppLinkNavigationTypeFailure;
}
+ (id<FBSDKAppLinkResolving>)defaultResolver {
if (defaultResolver) {
return defaultResolver;
}
return [FBSDKWebViewAppLinkResolver sharedInstance];
}
+ (void)setDefaultResolver:(id<FBSDKAppLinkResolving>)resolver {
defaultResolver = resolver;
}
@end
#endif

View File

@ -0,0 +1,72 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "FBSDKAppLinkResolving.h"
NS_ASSUME_NONNULL_BEGIN
/**
Describes the callback for appLinkFromURLInBackground.
@param appLinks the FBSDKAppLinks representing the deferred App Links
@param error the error during the request, if any
*/
typedef void (^FBSDKAppLinksBlock)(NSDictionary<NSURL *, FBSDKAppLink *> * appLinks,
NSError * _Nullable error)
NS_SWIFT_NAME(AppLinksBlock);
/**
Provides an implementation of the FBSDKAppLinkResolving protocol that uses the Facebook App Link
Index API to resolve App Links given a URL. It also provides an additional helper method that can resolve
multiple App Links in a single call.
Usage of this type requires a client token. See `[FBSDKSettings setClientToken:]`
*/
NS_SWIFT_NAME(AppLinkResolver)
@interface FBSDKAppLinkResolver : NSObject<FBSDKAppLinkResolving>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
Asynchronously resolves App Link data for a given array of URLs.
@param urls The URLs to resolve into an App Link.
@param handler The completion block that will return an App Link for the given URL.
*/
- (void)appLinksFromURLs:(NSArray<NSURL *> *)urls handler:(FBSDKAppLinksBlock)handler
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension");
/**
Allocates and initializes a new instance of FBSDKAppLinkResolver.
*/
+ (instancetype)resolver
NS_SWIFT_NAME(init());
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,180 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkResolver.h"
#import <UIKit/UIKit.h>
#import "FBSDKAccessToken.h"
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkTarget.h"
#import "FBSDKGraphRequest+Internal.h"
#import "FBSDKGraphRequestConnection.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKLogger.h"
#import "FBSDKSettings+Internal.h"
#import "FBSDKUtility.h"
static NSString *const kURLKey = @"url";
static NSString *const kIOSAppStoreIdKey = @"app_store_id";
static NSString *const kIOSAppNameKey = @"app_name";
static NSString *const kWebKey = @"web";
static NSString *const kIOSKey = @"ios";
static NSString *const kIPhoneKey = @"iphone";
static NSString *const kIPadKey = @"ipad";
static NSString *const kShouldFallbackKey = @"should_fallback";
static NSString *const kAppLinksKey = @"app_links";
@interface FBSDKAppLinkResolver ()
@property (nonatomic, strong) NSMutableDictionary<NSURL *, FBSDKAppLink *> *cachedFBSDKAppLinks;
@property (nonatomic, assign) UIUserInterfaceIdiom userInterfaceIdiom;
@end
@implementation FBSDKAppLinkResolver
+ (void)initialize
{
if (self == [FBSDKAppLinkResolver class]) {
}
}
- (instancetype)initWithUserInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
{
if (self = [super init]) {
self.cachedFBSDKAppLinks = [NSMutableDictionary dictionary];
self.userInterfaceIdiom = userInterfaceIdiom;
}
return self;
}
- (void)appLinkFromURL:(NSURL *)url handler:(FBSDKAppLinkBlock)handler
{
[self appLinksFromURLs:@[url] handler:^(NSDictionary<NSURL *, FBSDKAppLink *> *urls, NSError * _Nullable error) {
handler(urls[url], error);
}];
}
- (void)appLinksFromURLs:(NSArray<NSURL *> *)urls handler:(FBSDKAppLinksBlock)handler
{
if (![FBSDKSettings clientToken] && ![FBSDKAccessToken currentAccessToken]) {
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
logEntry:@"A user access token or clientToken is required to use FBAppLinkResolver"];
}
NSMutableDictionary<NSURL *, FBSDKAppLink *> *appLinks = [NSMutableDictionary dictionary];
NSMutableArray<NSURL *> *toFind = [NSMutableArray array];
NSMutableArray<NSString *> *toFindStrings = [NSMutableArray array];
@synchronized (self.cachedFBSDKAppLinks) {
for (NSURL *url in urls) {
if (self.cachedFBSDKAppLinks[url]) {
appLinks[url] = self.cachedFBSDKAppLinks[url];
} else {
[toFind addObject:url];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *toFindString = [url.absoluteString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
#pragma clang diagnostic pop
if (toFindString) {
[toFindStrings addObject:toFindString];
}
}
}
}
if (toFind.count == 0) {
// All of the URLs have already been found.
handler(_cachedFBSDKAppLinks, nil);
}
NSMutableArray<NSString *> *fields = [NSMutableArray arrayWithObject:kIOSKey];
NSString *idiomSpecificField = nil;
switch (self.userInterfaceIdiom) {
case UIUserInterfaceIdiomPad:
idiomSpecificField = kIPadKey;
break;
case UIUserInterfaceIdiomPhone:
idiomSpecificField = kIPhoneKey;
break;
default:
break;
}
if (idiomSpecificField) {
[fields addObject:idiomSpecificField];
}
NSString *path = [NSString stringWithFormat:@"?fields=%@.fields(%@)&ids=%@",
kAppLinksKey,
[fields componentsJoinedByString:@","],
[toFindStrings componentsJoinedByString:@","]];
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:path
parameters:nil
flags:FBSDKGraphRequestFlagDoNotInvalidateTokenOnError | FBSDKGraphRequestFlagDisableErrorRecovery];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (error) {
handler(@{}, error);
return;
}
for (NSURL *url in toFind) {
id nestedObject = result[url.absoluteString][kAppLinksKey];
NSMutableArray *rawTargets = [NSMutableArray array];
if (idiomSpecificField) {
[rawTargets addObjectsFromArray:nestedObject[idiomSpecificField]];
}
[rawTargets addObjectsFromArray:nestedObject[kIOSKey]];
NSMutableArray<FBSDKAppLinkTarget *> *targets = [NSMutableArray arrayWithCapacity:rawTargets.count];
for (id rawTarget in rawTargets) {
[targets addObject:[FBSDKAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:rawTarget[kURLKey]]
appStoreId:rawTarget[kIOSAppStoreIdKey]
appName:rawTarget[kIOSAppNameKey]]];
}
id webTarget = nestedObject[kWebKey];
NSString *webFallbackString = webTarget[kURLKey];
NSURL *fallbackUrl = webFallbackString ? [NSURL URLWithString:webFallbackString] : url;
NSNumber *shouldFallback = webTarget[kShouldFallbackKey];
if (shouldFallback != nil && !shouldFallback.boolValue) {
fallbackUrl = nil;
}
FBSDKAppLink *link = [FBSDKAppLink appLinkWithSourceURL:url
targets:targets
webURL:fallbackUrl];
@synchronized (self.cachedFBSDKAppLinks) {
self.cachedFBSDKAppLinks[url] = link;
}
appLinks[url] = link;
}
handler(appLinks, nil);
}];
}
+ (instancetype)resolver
{
return [[self alloc] initWithUserInterfaceIdiom:UI_USER_INTERFACE_IDIOM()];
}
@end
#endif

View File

@ -0,0 +1,60 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FBSDKAppLink;
/**
Describes the callback for appLinkFromURLInBackground.
@param appLink the FBSDKAppLink representing the deferred App Link
@param error the error during the request, if any
*/
typedef void (^FBSDKAppLinkBlock)(FBSDKAppLink * _Nullable appLink, NSError * _Nullable error)
NS_SWIFT_NAME(AppLinkBlock);
/**
Implement this protocol to provide an alternate strategy for resolving
App Links that may include pre-fetching, caching, or querying for App Link
data from an index provided by a service provider.
*/
NS_SWIFT_NAME(AppLinkResolving)
@protocol FBSDKAppLinkResolving <NSObject>
/**
Asynchronously resolves App Link data for a given URL.
@param url The URL to resolve into an App Link.
@param handler The completion block that will return an App Link for the given URL.
*/
- (void)appLinkFromURL:(NSURL *)url handler:(FBSDKAppLinkBlock)handler
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension");
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,114 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <UIKit/UIKit.h>
#import "FBSDKAppLinkReturnToRefererView.h"
NS_ASSUME_NONNULL_BEGIN
@class FBSDKAppLink;
@class FBSDKAppLinkReturnToRefererController;
/**
Protocol that a class can implement in order to be notified when the user has navigated back
to the referer of an App Link.
*/
NS_SWIFT_NAME(AppLinkReturnToRefererControllerDelegate)
@protocol FBSDKAppLinkReturnToRefererControllerDelegate <NSObject>
@optional
/** Called when the user has tapped to navigate, but before the navigation has been performed. */
- (void)returnToRefererController:(FBSDKAppLinkReturnToRefererController *)controller
willNavigateToAppLink:(FBSDKAppLink *)appLink
NS_SWIFT_NAME(return(to:willNavigateTo:));
/** Called after the navigation has been attempted, with an indication of whether the referer
app link was successfully opened. */
- (void)returnToRefererController:(FBSDKAppLinkReturnToRefererController *)controller
didNavigateToAppLink:(FBSDKAppLink *)url
type:(FBSDKAppLinkNavigationType)type
NS_SWIFT_NAME(return(to:didNavigateTo:type:));
@end
/**
A controller class that implements default behavior for a FBSDKAppLinkReturnToRefererView, including
the ability to display the view above the navigation bar for navigation-based apps.
*/
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension")
NS_SWIFT_NAME(AppLinkReturnToRefererController)
@interface FBSDKAppLinkReturnToRefererController : NSObject <FBSDKAppLinkReturnToRefererViewDelegate>
/**
The delegate that will be notified when the user navigates back to the referer.
*/
@property (nonatomic, weak, nullable) id<FBSDKAppLinkReturnToRefererControllerDelegate> delegate;
/**
The FBSDKAppLinkReturnToRefererView this controller is controlling.
*/
@property (nonatomic, strong) FBSDKAppLinkReturnToRefererView *view;
/**
Initializes a controller suitable for controlling a FBSDKAppLinkReturnToRefererView that is to be displayed
contained within another UIView (i.e., not displayed above the navigation bar).
*/
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
Initializes a controller suitable for controlling a FBSDKAppLinkReturnToRefererView that is to be displayed
displayed above the navigation bar.
@param navController The Navigation Controller for display above
*/
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController
NS_SWIFT_NAME(init(navController:));
/**
Removes the view entirely from the navigation controller it is currently displayed in.
*/
- (void)removeFromNavController;
/**
Shows the FBSDKAppLinkReturnToRefererView with the specified referer information. If nil or missing data,
the view will not be displayed. */
- (void)showViewForRefererAppLink:(FBSDKAppLink *)refererAppLink
NS_SWIFT_NAME(showView(forReferer:));
/**
Shows the FBSDKAppLinkReturnToRefererView with referer information extracted from the specified URL.
If nil or missing referer App Link data, the view will not be displayed. */
- (void)showViewForRefererURL:(NSURL *)url
NS_SWIFT_NAME(showView(forReferer:));
/**
Closes the view, possibly animating it.
*/
- (void)closeViewAnimated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,244 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkReturnToRefererController.h"
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkReturnToRefererView_Internal.h"
#import "FBSDKURL_Internal.h"
static const CFTimeInterval kFBSDKViewAnimationDuration = 0.25f;
@implementation FBSDKAppLinkReturnToRefererController {
UINavigationController *_navigationController;
FBSDKAppLinkReturnToRefererView *_view;
}
#pragma mark - Object lifecycle
- (instancetype)init {
return [super init];
}
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController {
self = [self init];
if (self) {
_navigationController = navController;
if (_navigationController != nil) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(statusBarFrameWillChange:)
name:UIApplicationWillChangeStatusBarFrameNotification
object:nil];
[nc addObserver:self
selector:@selector(statusBarFrameDidChange:)
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
[nc addObserver:self
selector:@selector(orientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
}
return self;
}
- (void)dealloc {
_view.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public API
- (FBSDKAppLinkReturnToRefererView *)view {
if (!_view) {
self.view = [[FBSDKAppLinkReturnToRefererView alloc] initWithFrame:CGRectZero];
if (_navigationController) {
[_navigationController.view addSubview:_view];
}
}
return _view;
}
- (void)setView:(FBSDKAppLinkReturnToRefererView *)view {
if (_view != view) {
_view.delegate = nil;
}
_view = view;
_view.delegate = self;
if (_navigationController) {
_view.includeStatusBarInSize = FBSDKIncludeStatusBarInSizeAlways;
}
}
- (void)showViewForRefererAppLink:(FBSDKAppLink *)refererAppLink {
self.view.refererAppLink = refererAppLink;
[_view sizeToFit];
if (_navigationController) {
if (!_view.closed) {
dispatch_async(dispatch_get_main_queue(), ^{
[self moveNavigationBar];
});
}
}
}
- (void)showViewForRefererURL:(NSURL *)url {
FBSDKAppLink *appLink = [FBSDKURL URLForRenderBackToReferrerBarURL:url].appLinkReferer;
[self showViewForRefererAppLink:appLink];
}
- (void)removeFromNavController {
if (_navigationController) {
[_view removeFromSuperview];
_navigationController = nil;
}
}
#pragma mark - FBSDKAppLinkReturnToRefererViewDelegate
- (void)returnToRefererViewDidTapInsideCloseButton:(FBSDKAppLinkReturnToRefererView *)view {
[self closeViewAnimated:YES explicitlyClosed:YES];
}
- (void)returnToRefererViewDidTapInsideLink:(FBSDKAppLinkReturnToRefererView *)view
link:(FBSDKAppLink *)link {
[self openRefererAppLink:link];
[self closeViewAnimated:NO explicitlyClosed:NO];
}
#pragma mark - Private
- (void)statusBarFrameWillChange:(NSNotification *)notification {
NSValue *rectValue = [notification.userInfo valueForKey:UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame;
[rectValue getValue:&newFrame];
if (_navigationController && !_view.closed) {
if (CGRectGetHeight(newFrame) == 40) {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:kFBSDKViewAnimationDuration delay:0.0 options:options animations:^{
self->_view.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(self->_view.bounds), 0.0);
} completion:nil];
}
}
}
- (void)statusBarFrameDidChange:(NSNotification *)notification {
NSValue *rectValue = [notification.userInfo valueForKey:UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame;
[rectValue getValue:&newFrame];
if (_navigationController && !_view.closed) {
if (CGRectGetHeight(newFrame) == 40) {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:kFBSDKViewAnimationDuration delay:0.0 options:options animations:^{
[self->_view sizeToFit];
[self moveNavigationBar];
} completion:nil];
}
}
}
- (void)orientationDidChange:(NSNotificationCenter *)notification {
if (_navigationController && !_view.closed && CGRectGetHeight(_view.bounds) > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self moveNavigationBar];
});
}
}
- (void)moveNavigationBar {
if (_view.closed || !_view.refererAppLink) {
return;
}
[self updateNavigationBarY:CGRectGetHeight(_view.bounds)];
}
- (void)updateNavigationBarY:(CGFloat)y {
UINavigationBar *navigationBar = _navigationController.navigationBar;
CGRect navigationBarFrame = navigationBar.frame;
CGFloat oldContainerViewY = CGRectGetMaxY(navigationBarFrame);
navigationBarFrame.origin.y = y;
navigationBar.frame = navigationBarFrame;
CGFloat dy = CGRectGetMaxY(navigationBarFrame) - oldContainerViewY;
UIView *containerView = _navigationController.visibleViewController.view.superview;
containerView.frame = UIEdgeInsetsInsetRect(containerView.frame, UIEdgeInsetsMake(dy, 0.0, 0.0, 0.0));
}
- (void)closeViewAnimated:(BOOL)animated {
[self closeViewAnimated:animated explicitlyClosed:YES];
}
- (void)closeViewAnimated:(BOOL)animated explicitlyClosed:(BOOL)explicitlyClosed {
void (^closer)(void) = ^{
if (self->_navigationController) {
[self updateNavigationBarY:self->_view.statusBarHeight];
}
CGRect frame = self->_view.frame;
frame.size.height = 0.0;
self->_view.frame = frame;
};
if (animated) {
[UIView animateWithDuration:kFBSDKViewAnimationDuration animations:^{
closer();
} completion:^(BOOL finished) {
if (explicitlyClosed) {
self->_view.closed = YES;
}
}];
} else {
closer();
if (explicitlyClosed) {
self->_view.closed = YES;
}
}
}
- (void)openRefererAppLink:(FBSDKAppLink *)refererAppLink {
if (refererAppLink) {
id<FBSDKAppLinkReturnToRefererControllerDelegate> delegate = _delegate;
if ([delegate respondsToSelector:@selector(returnToRefererController:willNavigateToAppLink:)]) {
[delegate returnToRefererController:self willNavigateToAppLink:refererAppLink];
}
NSError *error = nil;
FBSDKAppLinkNavigationType type = [FBSDKAppLinkNavigation navigateToAppLink:refererAppLink error:&error];
if ([delegate respondsToSelector:@selector(returnToRefererController:didNavigateToAppLink:type:)]) {
[delegate returnToRefererController:self didNavigateToAppLink:refererAppLink type:type];
}
}
}
@end
#endif

View File

@ -0,0 +1,99 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <UIKit/UIKit.h>
#import "FBSDKAppLinkNavigation.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, FBSDKIncludeStatusBarInSize) {
FBSDKIncludeStatusBarInSizeNever,
FBSDKIncludeStatusBarInSizeAlways,
} NS_SWIFT_NAME(FBAppLinkReturnToRefererView.StatusBarSizeInclude);
@class FBSDKAppLinkReturnToRefererView;
@class FBSDKURL;
/**
Protocol that a class can implement in order to be notified when the user has navigated back
to the referer of an App Link.
*/
NS_SWIFT_NAME(AppLinkReturnToRefererViewDelegate)
@protocol FBSDKAppLinkReturnToRefererViewDelegate <NSObject>
/**
Called when the user has tapped inside the close button.
*/
- (void)returnToRefererViewDidTapInsideCloseButton:(FBSDKAppLinkReturnToRefererView *)view
NS_SWIFT_NAME(returnToRefererViewDidTapInsideCloseButton(_:));
/**
Called when the user has tapped inside the App Link portion of the view.
*/
- (void)returnToRefererViewDidTapInsideLink:(FBSDKAppLinkReturnToRefererView *)view
link:(FBSDKAppLink *)link
NS_SWIFT_NAME(returnToRefererView(_:didTapInside:));
@end
/**
Provides a UIView that displays a button allowing users to navigate back to the
application that launched the App Link currently being handled, if the App Link
contained referer data. The user can also close the view by clicking a close button
rather than navigating away. If the view is provided an App Link that does not contain
referer data, it will have zero size and no UI will be displayed.
*/
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension")
NS_SWIFT_NAME(FBAppLinkReturnToRefererView)
@interface FBSDKAppLinkReturnToRefererView : UIView
/**
The delegate that will be notified when the user navigates back to the referer.
*/
@property (nonatomic, weak, nullable) id<FBSDKAppLinkReturnToRefererViewDelegate> delegate;
/**
The color of the text label and close button.
*/
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) FBSDKAppLink *refererAppLink;
/**
Indicates whether to extend the size of the view to include the current status bar
size, for use in scenarios where the view might extend under the status bar on iOS 7 and
above; this property has no effect on earlier versions of iOS.
*/
@property (nonatomic, assign) FBSDKIncludeStatusBarInSize includeStatusBarInSize
NS_SWIFT_NAME(statusBarSizeInclude);
/**
Indicates whether the user has closed the view by clicking the close button.
*/
@property (nonatomic, assign, getter=isClosed) BOOL closed;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,272 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkReturnToRefererView.h"
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkTarget.h"
static const CGFloat FBSDKMarginX = 8.5f;
static const CGFloat FBSDKMarginY = 8.5f;
static NSString *const FBSDKRefererAppLink = @"referer_app_link";
static NSString *const FBSDKRefererAppName = @"app_name";
static NSString *const FBSDKRefererUrl = @"url";
static const CGFloat FBSDKCloseButtonWidth = 12.0;
static const CGFloat FBSDKCloseButtonHeight = 12.0;
@interface FBSDKAppLinkReturnToRefererView ()
@property (nonatomic, strong) UILabel *labelView;
@property (nonatomic, strong) UIButton *closeButton;
@property (nonatomic, strong) UITapGestureRecognizer *insideTapGestureRecognizer;
@end
@implementation FBSDKAppLinkReturnToRefererView {
BOOL _explicitlyHidden;
}
#pragma mark - Initialization
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
[self sizeToFit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
// Initialization code
_includeStatusBarInSize = FBSDKIncludeStatusBarInSizeAlways;
// iOS 7 system blue color
self.backgroundColor = [UIColor colorWithRed:0.0f green:122.0f / 255.0f blue:1.0f alpha:1.0f];
self.textColor = [UIColor whiteColor];
self.clipsToBounds = YES;
[self initViews];
}
- (void)initViews {
if (!_labelView && !_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
_closeButton.backgroundColor = [UIColor clearColor];
_closeButton.userInteractionEnabled = YES;
_closeButton.clipsToBounds = YES;
_closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
_closeButton.contentMode = UIViewContentModeCenter;
[_closeButton addTarget:self action:@selector(closeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_closeButton];
_labelView = [[UILabel alloc] initWithFrame:CGRectZero];
_labelView.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
_labelView.textColor = [UIColor whiteColor];
_labelView.backgroundColor = [UIColor clearColor];
_labelView.textAlignment = NSTextAlignmentCenter;
_labelView.clipsToBounds = YES;
[self updateLabelText];
[self addSubview:_labelView];
_insideTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInside:)];
_labelView.userInteractionEnabled = YES;
[_labelView addGestureRecognizer:_insideTapGestureRecognizer];
[self updateColors];
}
}
#pragma mark - Layout
- (CGSize)intrinsicContentSize {
CGSize size = self.bounds.size;
if (_closed || !self.hasRefererData) {
size.height = 0.0;
} else {
CGSize labelSize = [_labelView sizeThatFits:size];
size = CGSizeMake(size.width, labelSize.height + 2 * FBSDKMarginY + self.statusBarHeight);
}
return size;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect bounds = self.bounds;
_labelView.preferredMaxLayoutWidth = _labelView.bounds.size.width;
CGSize labelSize = [_labelView sizeThatFits:bounds.size];
_labelView.frame = CGRectMake(FBSDKMarginX,
CGRectGetMaxY(bounds) - labelSize.height - 1.5f * FBSDKMarginY,
CGRectGetMaxX(bounds) - FBSDKCloseButtonWidth - 3 * FBSDKMarginX,
labelSize.height + FBSDKMarginY);
_closeButton.frame = CGRectMake(CGRectGetMaxX(bounds) - FBSDKCloseButtonWidth - 2 * FBSDKMarginX,
_labelView.center.y - FBSDKCloseButtonHeight / 2.0f - FBSDKMarginY,
FBSDKCloseButtonWidth + 2 * FBSDKMarginX,
FBSDKCloseButtonHeight + 2 * FBSDKMarginY);
}
- (CGSize)sizeThatFits:(CGSize)size {
if (_closed || !self.hasRefererData) {
size = CGSizeMake(size.width, 0.0);
} else {
CGSize labelSize = [_labelView sizeThatFits:size];
size = CGSizeMake(size.width, labelSize.height + 2 * FBSDKMarginY + self.statusBarHeight);
}
return size;
}
- (CGFloat)statusBarHeight {
UIApplication *application = [UIApplication sharedApplication];
BOOL include;
switch (_includeStatusBarInSize) {
case FBSDKIncludeStatusBarInSizeAlways:
include = YES;
break;
case FBSDKIncludeStatusBarInSizeNever:
include = NO;
break;
}
if (include && !application.statusBarHidden) {
BOOL landscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
CGRect statusBarFrame = application.statusBarFrame;
return landscape ? CGRectGetWidth(statusBarFrame) : CGRectGetHeight(statusBarFrame);
}
return 0;
}
#pragma mark - Public API
- (void)setIncludeStatusBarInSize:(FBSDKIncludeStatusBarInSize)includeStatusBarInSize {
_includeStatusBarInSize = includeStatusBarInSize;
[self setNeedsLayout];
[self invalidateIntrinsicContentSize];
}
- (void)setTextColor:(UIColor *)textColor {
_textColor = textColor;
[self updateColors];
}
- (void)setRefererAppLink:(FBSDKAppLink *)refererAppLink {
_refererAppLink = refererAppLink;
[self updateLabelText];
[self updateHidden];
[self invalidateIntrinsicContentSize];
}
- (void)setClosed:(BOOL)closed {
if (_closed != closed) {
_closed = closed;
[self updateHidden];
[self invalidateIntrinsicContentSize];
}
}
- (void)setHidden:(BOOL)hidden {
_explicitlyHidden = hidden;
[self updateHidden];
}
#pragma mark - Private
- (void)updateLabelText {
NSString *appName = (_refererAppLink && _refererAppLink.targets[0]) ? _refererAppLink.targets[0].appName : nil;
_labelView.text = [self localizedLabelForReferer:appName];
}
- (void)updateColors {
UIImage *closeButtonImage = [self drawCloseButtonImageWithColor:_textColor];
_labelView.textColor = _textColor;
[_closeButton setImage:closeButtonImage forState:UIControlStateNormal];
}
- (UIImage *)drawCloseButtonImageWithColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(FBSDKCloseButtonWidth, FBSDKCloseButtonHeight), NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextSetLineWidth(context, 1.25f);
CGFloat inset = 0.5f;
CGContextMoveToPoint(context, inset, inset);
CGContextAddLineToPoint(context, FBSDKCloseButtonWidth - inset, FBSDKCloseButtonHeight - inset);
CGContextStrokePath(context);
CGContextMoveToPoint(context, FBSDKCloseButtonWidth - inset, inset);
CGContextAddLineToPoint(context, inset, FBSDKCloseButtonHeight - inset);
CGContextStrokePath(context);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
- (NSString *)localizedLabelForReferer:(NSString *)refererName {
if (!refererName) {
return nil;
}
NSString *format = NSLocalizedString(@"Touch to return to %1$@", @"Format for the string to return to a calling app.");
return [NSString stringWithFormat:format, refererName];
}
- (BOOL)hasRefererData {
return _refererAppLink && _refererAppLink.targets[0];
}
- (void)closeButtonTapped:(id)sender {
[_delegate returnToRefererViewDidTapInsideCloseButton:self];
}
- (void)onTapInside:(UIGestureRecognizer *)sender {
[_delegate returnToRefererViewDidTapInsideLink:self link:_refererAppLink];
}
- (void)updateHidden {
super.hidden = _explicitlyHidden || _closed || !self.hasRefererData;
}
@end
#endif

View File

@ -0,0 +1,56 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Represents a target defined in App Link metadata, consisting of at least
a URL, and optionally an App Store ID and name.
*/
NS_SWIFT_NAME(AppLinkTarget)
@interface FBSDKAppLinkTarget : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/** Creates a FBSDKAppLinkTarget with the given app site and target URL. */
+ (instancetype)appLinkTargetWithURL:(nullable NSURL *)url
appStoreId:(nullable NSString *)appStoreId
appName:(NSString *)appName
NS_SWIFT_NAME(init(url:appStoreId:appName:));
/** The URL prefix for this app link target */
@property (nonatomic, strong, readonly, nullable) NSURL *URL;
/** The app ID for the app store */
@property (nonatomic, copy, readonly, nullable) NSString *appStoreId;
/** The name of the app */
@property (nonatomic, copy, readonly) NSString *appName;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,47 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkTarget.h"
@interface FBSDKAppLinkTarget ()
@property (nonatomic, strong) NSURL *URL;
@property (nonatomic, copy) NSString *appStoreId;
@property (nonatomic, copy) NSString *appName;
@end
@implementation FBSDKAppLinkTarget
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
appStoreId:(NSString *)appStoreId
appName:(NSString *)appName {
FBSDKAppLinkTarget *target = [[self alloc] init];
target.URL = url;
target.appStoreId = appStoreId;
target.appName = appName;
return target;
}
@end
#endif

View File

@ -0,0 +1,93 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Describes the callback for fetchDeferredAppLink.
@param url the url representing the deferred App Link
@param error the error during the request, if any
The url may also have a fb_click_time_utc query parameter that
represents when the click occurred that caused the deferred App Link to be created.
*/
typedef void (^FBSDKURLBlock)(NSURL *_Nullable url, NSError *_Nullable error)
NS_SWIFT_NAME(URLBlock);
/**
Class containing App Links related utility methods.
*/
NS_SWIFT_NAME(AppLinkUtility)
@interface FBSDKAppLinkUtility : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
Call this method from the main thread to fetch deferred applink data if you use Mobile App
Engagement Ads (https://developers.facebook.com/docs/ads-for-apps/mobile-app-ads-engagement).
This may require a network round trip. If successful, the handler is invoked with the link
data (this will only return a valid URL once, and future calls will result in a nil URL
value in the callback).
@param handler the handler to be invoked if there is deferred App Link data
The handler may contain an NSError instance to capture any errors. In the
common case where there simply was no app link data, the NSError instance will be nil.
This method should only be called from a location that occurs after any launching URL has
been processed (e.g., you should call this method from your application delegate's
applicationDidBecomeActive:).
*/
+ (void)fetchDeferredAppLink:(nullable FBSDKURLBlock)handler;
/*
Call this method to fetch promotion code from the url, if it's present.
@param url App Link url that was passed to the app.
@return Promotion code string.
Call this method to fetch App Invite Promotion Code from applink if present.
This can be used to fetch the promotion code that was associated with the invite when it
was created. This method should be called with the url from the openURL method.
*/
+ (nullable NSString *)appInvitePromotionCodeFromURL:(NSURL *)url;
/**
Check whether the scheme is defined in the app's URL schemes.
@param scheme the scheme of App Link URL
@return YES if the scheme is defined, otherwise NO.
*/
+ (BOOL)isMatchURLScheme:(NSString *)scheme;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,125 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkUtility.h"
#import "FBSDKAppEventsUtility.h"
#import "FBSDKGraphRequest.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKSettings.h"
#import "FBSDKURL.h"
#import "FBSDKUtility.h"
static NSString *const FBSDKLastDeferredAppLink = @"com.facebook.sdk:lastDeferredAppLink%@";
static NSString *const FBSDKDeferredAppLinkEvent = @"DEFERRED_APP_LINK";
@implementation FBSDKAppLinkUtility {}
+ (void)fetchDeferredAppLink:(FBSDKURLBlock)handler
{
NSAssert([NSThread isMainThread], @"FBSDKAppLink fetchDeferredAppLink: must be invoked from main thread.");
NSString *appID = [FBSDKSettings appID];
// Deferred app links are only currently used for engagement ads, thus we consider the app to be an advertising one.
// If this is considered for organic, non-ads scenarios, we'll need to retrieve the FBAppEventsUtility.shouldAccessAdvertisingID
// before we make this call.
NSMutableDictionary *deferredAppLinkParameters =
[FBSDKAppEventsUtility activityParametersDictionaryForEvent:FBSDKDeferredAppLinkEvent
implicitEventsOnly:NO
shouldAccessAdvertisingID:YES];
FBSDKGraphRequest *deferredAppLinkRequest = [[FBSDKGraphRequest alloc] initWithGraphPath:[NSString stringWithFormat:@"%@/activities", appID, nil]
parameters:deferredAppLinkParameters
tokenString:nil
version:nil
HTTPMethod:FBSDKHTTPMethodPOST];
[deferredAppLinkRequest startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection,
id result,
NSError *error) {
NSURL *applinkURL = nil;
if (!error) {
NSString *appLinkString = result[@"applink_url"];
if (appLinkString) {
applinkURL = [NSURL URLWithString:appLinkString];
NSString *createTimeUtc = result[@"click_time"];
if (createTimeUtc) {
// append/translate the create_time_utc so it can be used by clients
NSString *modifiedURLString = [applinkURL.absoluteString
stringByAppendingFormat:@"%@fb_click_time_utc=%@",
(applinkURL.query) ? @"&" : @"?" ,
createTimeUtc];
applinkURL = [NSURL URLWithString:modifiedURLString];
}
}
}
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(applinkURL, error);
});
}
}];
}
+ (NSString *)appInvitePromotionCodeFromURL:(NSURL *)url
{
FBSDKURL *parsedUrl = [FBSDKURL URLWithURL:url];
NSDictionary *extras = parsedUrl.appLinkExtras;
if (extras) {
NSString *deeplinkContextString = extras[@"deeplink_context"];
// Parse deeplinkContext and extract promo code
if (deeplinkContextString.length > 0) {
NSError *error = nil;
NSDictionary<id, id> *deeplinkContextData = [FBSDKBasicUtility objectForJSONString:deeplinkContextString error:&error];
if (!error && [deeplinkContextData isKindOfClass:[NSDictionary class]]) {
return deeplinkContextData[@"promo_code"];
}
}
}
return nil;
}
+ (BOOL)isMatchURLScheme:(NSString *)scheme
{
if (!scheme) {
return NO;
}
for(NSDictionary *urlType in [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"])
{
for(NSString *urlScheme in urlType[@"CFBundleURLSchemes"]) {
if([urlScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
return YES;
}
}
}
return NO;
}
@end
#endif

View File

@ -0,0 +1,46 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "FBSDKAppLinkResolving.h"
NS_ASSUME_NONNULL_BEGIN
/**
A reference implementation for an App Link resolver that uses a hidden WKWebView
to parse the HTML containing App Link metadata.
*/
NS_SWIFT_NAME(WebViewAppLinkResolver)
@interface FBSDKWebViewAppLinkResolver : NSObject <FBSDKAppLinkResolving>
/**
Gets the instance of a FBSDKWebViewAppLinkResolver.
*/
@property (class, nonatomic, readonly, strong) FBSDKWebViewAppLinkResolver *sharedInstance
NS_SWIFT_NAME(shared);
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,325 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKWebViewAppLinkResolver.h"
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkTarget.h"
/**
Describes the callback for appLinkFromURLInBackground.
@param result the results from following redirects
@param error the error during the request, if any
*/
typedef void (^FBSDKURLFollowRedirectsBlock)(NSDictionary<NSString *, id> *result, NSError * _Nullable error)
NS_SWIFT_NAME(URLFollowRedirectsBlock);
// Defines JavaScript to extract app link tags from HTML content
static NSString *const FBSDKWebViewAppLinkResolverTagExtractionJavaScript = @""
"(function() {"
" var metaTags = document.getElementsByTagName('meta');"
" var results = [];"
" for (var i = 0; i < metaTags.length; i++) {"
" var property = metaTags[i].getAttribute('property');"
" if (property && property.substring(0, 'al:'.length) === 'al:') {"
" var tag = { \"property\": metaTags[i].getAttribute('property') };"
" if (metaTags[i].hasAttribute('content')) {"
" tag['content'] = metaTags[i].getAttribute('content');"
" }"
" results.push(tag);"
" }"
" }"
" return JSON.stringify(results);"
"})()";
static NSString *const FBSDKWebViewAppLinkResolverIOSURLKey = @"url";
static NSString *const FBSDKWebViewAppLinkResolverIOSAppStoreIdKey = @"app_store_id";
static NSString *const FBSDKWebViewAppLinkResolverIOSAppNameKey = @"app_name";
static NSString *const FBSDKWebViewAppLinkResolverDictionaryValueKey = @"_value";
static NSString *const FBSDKWebViewAppLinkResolverPreferHeader = @"Prefer-Html-Meta-Tags";
static NSString *const FBSDKWebViewAppLinkResolverMetaTagPrefix = @"al";
static NSString *const FBSDKWebViewAppLinkResolverWebKey = @"web";
static NSString *const FBSDKWebViewAppLinkResolverIOSKey = @"ios";
static NSString *const FBSDKWebViewAppLinkResolverIPhoneKey = @"iphone";
static NSString *const FBSDKWebViewAppLinkResolverIPadKey = @"ipad";
static NSString *const FBSDKWebViewAppLinkResolverWebURLKey = @"url";
static NSString *const FBSDKWebViewAppLinkResolverShouldFallbackKey = @"should_fallback";
@interface FBSDKWebViewAppLinkResolverWebViewDelegate : NSObject <WKNavigationDelegate>
@property (nonatomic, copy) void (^didFinishLoad)(WKWebView *webView);
@property (nonatomic, copy) void (^didFailLoadWithError)(WKWebView *webView, NSError *error);
@property (nonatomic, assign) BOOL hasLoaded;
@end
@implementation FBSDKWebViewAppLinkResolverWebViewDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
if (self.didFinishLoad) {
self.didFinishLoad(webView);
}
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
if (self.didFailLoadWithError) {
self.didFailLoadWithError(webView, error);
}
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
if (self.hasLoaded) {
self.didFinishLoad(webView);
decisionHandler(WKNavigationActionPolicyCancel);
}
self.hasLoaded = YES;
decisionHandler(WKNavigationActionPolicyAllow);
}
@end
@implementation FBSDKWebViewAppLinkResolver
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (void)followRedirects:(NSURL *)url handler:(FBSDKURLFollowRedirectsBlock)handler
{
// This task will be resolved with either the redirect NSURL
// or a dictionary with the response data to be returned.
void (^completion)(NSURLResponse *response, NSData *data, NSError *error) = ^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
handler(nil, error);
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// NSURLConnection usually follows redirects automatically, but the
// documentation is unclear what the default is. This helps it along.
if (httpResponse.statusCode >= 300 && httpResponse.statusCode < 400) {
NSString *redirectString = httpResponse.allHeaderFields[@"Location"];
NSURL *redirectURL = [NSURL URLWithString:redirectString];
[self followRedirects:redirectURL handler:handler];
return;
}
}
handler(@{ @"response" : response, @"data" : data }, nil);
};
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:FBSDKWebViewAppLinkResolverMetaTagPrefix forHTTPHeaderField:FBSDKWebViewAppLinkResolverPreferHeader];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
completion(response, data, error);
}] resume];
}
- (void)appLinkFromURL:(NSURL *)url handler:(FBSDKAppLinkBlock)handler
{
dispatch_async(dispatch_get_main_queue(), ^{
[self followRedirects:url handler:^(NSDictionary<NSString *,id> *result, NSError * _Nullable error) {
if (error) {
handler(nil, error);
return;
}
NSData *responseData = result[@"data"];
NSHTTPURLResponse *response = result[@"response"];
WKWebView *webView = [[WKWebView alloc] init];
FBSDKWebViewAppLinkResolverWebViewDelegate *listener = [[FBSDKWebViewAppLinkResolverWebViewDelegate alloc] init];
__block FBSDKWebViewAppLinkResolverWebViewDelegate *retainedListener = listener;
listener.didFinishLoad = ^(WKWebView *view) {
if (retainedListener) {
[self getALDataFromLoadedPage:view handler:^(NSDictionary<NSString *,id> *ogData) {
[view removeFromSuperview];
view.navigationDelegate = nil;
retainedListener = nil;
handler([self appLinkFromALData:ogData destination:url], nil);
}];
}
};
listener.didFailLoadWithError = ^(WKWebView *view, NSError *loadError) {
if (retainedListener) {
[view removeFromSuperview];
view.navigationDelegate = nil;
retainedListener = nil;
handler(nil, loadError);
}
};
webView.navigationDelegate = listener;
webView.hidden = YES;
if (@available(iOS 9.0, *)) {
[webView loadData:responseData
MIMEType:response.MIMEType
characterEncodingName:response.textEncodingName
baseURL:response.URL];
} else {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:FBSDKWebViewAppLinkResolverMetaTagPrefix forHTTPHeaderField:FBSDKWebViewAppLinkResolverPreferHeader];
[webView loadRequest:request];
}
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;
[window addSubview:webView];
}];
});
}
/*
Builds up a data structure filled with the app link data from the meta tags on a page.
The structure of this object is a dictionary where each key holds an array of app link
data dictionaries. Values are stored in a key called "_value".
*/
- (NSDictionary<NSString *, id> *)parseALData:(NSArray<NSDictionary<NSString *, id> *> *)dataArray {
NSMutableDictionary<NSString *, id> *al = [NSMutableDictionary dictionary];
for (NSDictionary<NSString *, id> *tag in dataArray) {
NSString *name = tag[@"property"];
if (![name isKindOfClass:[NSString class]]) {
continue;
}
NSArray<NSString *> *nameComponents = [name componentsSeparatedByString:@":"];
if (![nameComponents[0] isEqualToString:FBSDKWebViewAppLinkResolverMetaTagPrefix]) {
continue;
}
NSMutableDictionary<NSString *, id> *root = al;
for (NSUInteger i = 1; i < nameComponents.count; i++) {
NSMutableArray<NSMutableDictionary<NSString *, id> *> *children = root[nameComponents[i]];
if (!children) {
children = [NSMutableArray array];
root[nameComponents[i]] = children;
}
NSMutableDictionary<NSString *, id> *child = children.lastObject;
if (!child || i == nameComponents.count - 1) {
child = [NSMutableDictionary dictionary];
[children addObject:child];
}
root = child;
}
if (tag[@"content"]) {
root[FBSDKWebViewAppLinkResolverDictionaryValueKey] = tag[@"content"];
}
}
return al;
}
- (void)getALDataFromLoadedPage:(WKWebView *)webView
handler:(void (^)(NSDictionary<NSString *, id> *))handler
{
// Run some JavaScript in the webview to fetch the meta tags.
[webView evaluateJavaScript:FBSDKWebViewAppLinkResolverTagExtractionJavaScript
completionHandler:^(id _Nullable evaluateResult, NSError * _Nullable error) {
NSString *jsonString = [evaluateResult isKindOfClass:[NSString class]] ? evaluateResult : nil;
error = nil;
NSArray<NSDictionary<NSString *, id> *> *arr =
[NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
handler([self parseALData:arr]);
}];
}
/*
Converts app link data into a FBSDKAppLink containing the targets relevant for this platform.
*/
- (FBSDKAppLink *)appLinkFromALData:(NSDictionary<NSString *, id> *)appLinkDict destination:(NSURL *)destination {
NSMutableArray<FBSDKAppLinkTarget *> *linkTargets = [NSMutableArray array];
NSArray *platformData = nil;
const UIUserInterfaceIdiom idiom = UI_USER_INTERFACE_IDIOM();
if (idiom == UIUserInterfaceIdiomPad) {
platformData = @[ appLinkDict[FBSDKWebViewAppLinkResolverIPadKey] ?: @{},
appLinkDict[FBSDKWebViewAppLinkResolverIOSKey] ?: @{} ];
} else if (idiom == UIUserInterfaceIdiomPhone) {
platformData = @[ appLinkDict[FBSDKWebViewAppLinkResolverIPhoneKey] ?: @{},
appLinkDict[FBSDKWebViewAppLinkResolverIOSKey] ?: @{} ];
} else {
// Future-proofing. Other User Interface idioms should only hit ios.
platformData = @[ appLinkDict[FBSDKWebViewAppLinkResolverIOSKey] ?: @{} ];
}
for (NSArray<NSDictionary *> *platformObjects in platformData) {
for (NSDictionary<NSString *, NSArray *> *platformDict in platformObjects) {
// The schema requires a single url/app store id/app name,
// but we could find multiple of them. We'll make a best effort
// to interpret this data.
NSArray<NSDictionary<NSString *, id> *> *urls = platformDict[FBSDKWebViewAppLinkResolverIOSURLKey];
NSArray<NSDictionary<NSString *, id> *> *appStoreIds = platformDict[FBSDKWebViewAppLinkResolverIOSAppStoreIdKey];
NSArray<NSDictionary<NSString *, id> *> *appNames = platformDict[FBSDKWebViewAppLinkResolverIOSAppNameKey];
NSUInteger maxCount = MAX(urls.count, MAX(appStoreIds.count, appNames.count));
for (NSUInteger i = 0; i < maxCount; i++) {
NSString *urlString = urls[i][FBSDKWebViewAppLinkResolverDictionaryValueKey];
NSURL *url = urlString ? [NSURL URLWithString:urlString] : nil;
NSString *appStoreId = appStoreIds[i][FBSDKWebViewAppLinkResolverDictionaryValueKey];
NSString *appName = appNames[i][FBSDKWebViewAppLinkResolverDictionaryValueKey];
FBSDKAppLinkTarget *target = [FBSDKAppLinkTarget appLinkTargetWithURL:url
appStoreId:appStoreId
appName:appName];
[linkTargets addObject:target];
}
}
}
NSDictionary<NSString *, id> *webDict = appLinkDict[FBSDKWebViewAppLinkResolverWebKey][0];
NSString *webUrlString = webDict[FBSDKWebViewAppLinkResolverWebURLKey][0][FBSDKWebViewAppLinkResolverDictionaryValueKey];
NSString *shouldFallbackString = webDict[FBSDKWebViewAppLinkResolverShouldFallbackKey][0][FBSDKWebViewAppLinkResolverDictionaryValueKey];
NSURL *webUrl = destination;
if (shouldFallbackString &&
[@[ @"no", @"false", @"0" ] containsObject:shouldFallbackString.lowercaseString]) {
webUrl = nil;
}
if (webUrl && webUrlString) {
webUrl = [NSURL URLWithString:webUrlString];
}
return [FBSDKAppLink appLinkWithSourceURL:destination
targets:linkTargets
webURL:webUrl];
}
@end
#endif

View File

@ -0,0 +1,37 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(MeasurementEventListener)
@interface FBSDKMeasurementEventListener : NSObject
@property (class, nonatomic, strong, readonly) FBSDKMeasurementEventListener *defaultListener
NS_SWIFT_NAME(default);
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,92 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKMeasurementEventListener.h"
#import "FBSDKAppEvents+Internal.h"
#import "FBSDKTimeSpentData.h"
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
static NSNotificationName const FBSDKMeasurementEventNotification = @"com.facebook.facebook-objc-sdk.measurement_event";
#else
static NSString *const FBSDKMeasurementEventNotification = @"com.facebook.facebook-objc-sdk.measurement_event";
#endif
static NSString *const FBSDKMeasurementEventName = @"event_name";
static NSString *const FBSDKMeasurementEventArgs = @"event_args";
static NSString *const FBSDKMeasurementEventPrefix = @"bf_";
@implementation FBSDKMeasurementEventListener
+ (instancetype)defaultListener
{
static dispatch_once_t dispatchOnceLocker = 0;
static FBSDKMeasurementEventListener *defaultListener = nil;
dispatch_once(&dispatchOnceLocker, ^{
defaultListener = [[self alloc] init];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:defaultListener
selector:@selector(logFBAppEventForNotification:)
name:FBSDKMeasurementEventNotification
object:nil];
});
return defaultListener;
}
- (void)logFBAppEventForNotification:(NSNotification *)note
{
// when catch al_nav_in event, we set source application for FBAppEvents.
if ([note.userInfo[FBSDKMeasurementEventName] isEqualToString:@"al_nav_in"]) {
NSString *sourceApplication = note.userInfo[FBSDKMeasurementEventArgs][@"sourceApplication"];
if (sourceApplication) {
[FBSDKTimeSpentData setSourceApplication:sourceApplication isFromAppLink:YES];
}
}
NSDictionary<NSString *, id> *eventArgs = note.userInfo[FBSDKMeasurementEventArgs];
NSMutableDictionary<NSString *, id> *logData = [[NSMutableDictionary alloc] init];
for (NSString *key in eventArgs.allKeys) {
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^0-9a-zA-Z _-]" options:0 error:&error];
NSString *safeKey = [regex stringByReplacingMatchesInString:key
options:0
range:NSMakeRange(0, key.length)
withTemplate:@"-"];
safeKey = [safeKey stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -"]];
logData[safeKey] = eventArgs[key];
}
[FBSDKAppEvents logInternalEvent:[FBSDKMeasurementEventPrefix stringByAppendingString:note.userInfo[FBSDKMeasurementEventName]]
parameters:logData
isImplicitlyLogged:YES];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
#endif

View File

@ -0,0 +1,35 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "FBSDKCrashObserving.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKCrashHandler : NSObject
+ (void)disable;
+ (void)addObserver:(id<FBSDKCrashObserving>)observer;
+ (void)removeObserver:(id<FBSDKCrashObserving>)observer;
+ (void)clearCrashReportFiles;
+ (NSString *)getFBSDKVersion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,347 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKCrashHandler.h"
#import <sys/utsname.h>
#import <UIKit/UIKit.h>
#import "FBSDKLibAnalyzer.h"
#define FBSDK_MAX_CRASH_LOGS 5
#define FBSDK_CRASH_PATH_NAME @"instrument"
#ifndef FBSDK_VERSION_STRING
#define FBSDK_VERSION_STRING @"5.15.1"
#endif
static NSUncaughtExceptionHandler *previousExceptionHandler = NULL;
static NSString *mappingTableIdentifier = NULL;
static NSString *directoryPath;
NSString *const kFBSDKAppVersion = @"app_version";
NSString *const kFBSDKCallstack = @"callstack";
NSString *const kFBSDKCrashReason = @"reason";
NSString *const kFBSDKCrashTimestamp = @"timestamp";
NSString *const kFBSDKDeviceModel = @"device_model";
NSString *const kFBSDKDeviceOSVersion = @"device_os_version";
NSString *const kFBSDKMapingTable = @"mapping_table";
NSString *const kFBSDKMappingTableIdentifier = @"mapping_table_identifier";
@implementation FBSDKCrashHandler
static NSHashTable<id<FBSDKCrashObserving>> *_observers;
static NSArray<NSDictionary<NSString *, id> *> *_processedCrashLogs;
static BOOL _isTurnedOff;
# pragma mark - Class Methods
+ (void)initialize
{
NSString *dirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:FBSDK_CRASH_PATH_NAME];
if (![[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:NO attributes:NULL error:NULL];
}
directoryPath = dirPath;
NSString *identifier = [[NSUUID UUID] UUIDString];
mappingTableIdentifier = [identifier stringByReplacingOccurrencesOfString:@"-" withString:@""];
_observers = [[NSHashTable alloc] init];
}
+ (void)sendCrashLogs
{
NSArray<id<FBSDKCrashObserving>> *observers = [_observers copy];
for (id<FBSDKCrashObserving> observer in observers) {
if (observer && [observer respondsToSelector:@selector(didReceiveCrashLogs:)]) {
NSArray<NSDictionary<NSString *, id> *> *filteredCrashLogs = [self filterCrashLogs:observer.prefixes];
[observer didReceiveCrashLogs:filteredCrashLogs];
}
}
}
+ (NSArray<NSDictionary<NSString *, id> *> *)filterCrashLogs:(NSArray<NSString *> *)prefixList
{
NSMutableArray<NSDictionary<NSString *, id> *> *crashLogs = [NSMutableArray array];
for (NSDictionary<NSString *, id> *crashLog in _processedCrashLogs) {
NSArray<NSString *> *callstack = crashLog[kFBSDKCallstack];
if ([self callstack:callstack containsPrefix:prefixList]) {
[crashLogs addObject:crashLog];
}
}
return crashLogs;
}
+ (BOOL)callstack:(NSArray<NSString *> *)callstack
containsPrefix:(NSArray<NSString *> *)prefixList
{
NSString *callStackString = [callstack componentsJoinedByString:@""];
for (NSString *prefix in prefixList) {
if ([callStackString containsString:prefix]) {
return YES;
}
}
return NO;
}
+ (void)disable
{
_isTurnedOff = YES;
[FBSDKCrashHandler uninstallExceptionsHandler];
_observers = nil;
}
+ (void)addObserver:(id<FBSDKCrashObserving>)observer
{
if (_isTurnedOff || ![self isSafeToGenerateMapping]) {
return;
}
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
[FBSDKCrashHandler installExceptionsHandler];
_processedCrashLogs = [self getProcessedCrashLogs];
});
if (![_observers containsObject:observer]) {
[_observers addObject:observer];
[self generateMethodMapping:observer];
[self sendCrashLogs];
}
}
+ (void)removeObserver:(id<FBSDKCrashObserving>)observer
{
if ([_observers containsObject:observer]) {
[_observers removeObject:observer];
if (_observers.count == 0) {
[FBSDKCrashHandler uninstallExceptionsHandler];
}
}
}
# pragma mark handler function
+ (void)installExceptionsHandler
{
NSUncaughtExceptionHandler *currentHandler = NSGetUncaughtExceptionHandler();
if (currentHandler != FBSDKExceptionHandler) {
previousExceptionHandler = currentHandler;
NSSetUncaughtExceptionHandler(&FBSDKExceptionHandler);
}
}
+ (void)uninstallExceptionsHandler
{
NSSetUncaughtExceptionHandler(previousExceptionHandler);
previousExceptionHandler = nil;
}
static void FBSDKExceptionHandler(NSException *exception)
{
[FBSDKCrashHandler saveException:exception];
if (previousExceptionHandler) {
previousExceptionHandler(exception);
}
}
#pragma mark - Storage
+ (void)saveException:(NSException *)exception
{
if (exception.callStackSymbols && exception.name) {
NSArray<NSString *> *stackSymbols = [NSArray arrayWithArray:exception.callStackSymbols];
[self saveCrashLog:@{
kFBSDKCallstack : stackSymbols,
kFBSDKCrashReason : exception.name,
}];
}
}
+ (void)saveSignal:(int)signal withCallStack:(NSArray<NSString *> *)callStack
{
if (callStack) {
NSString *signalDescription = [NSString stringWithCString:strsignal(signal) encoding:NSUTF8StringEncoding] ?: [NSString stringWithFormat:@"SIGNUM(%i)", signal];
[self saveCrashLog:@{
kFBSDKCallstack : callStack,
kFBSDKCrashReason : signalDescription,
}];
}
}
+ (NSArray<NSDictionary<NSString *, id> *> *)getProcessedCrashLogs
{
NSArray<NSDictionary<NSString *, id> *> *crashLogs = [self loadCrashLogs];
if (0 == crashLogs.count) {
[self clearCrashReportFiles];
return nil;
}
NSMutableArray<NSDictionary<NSString *, id> *> *processedCrashLogs = [NSMutableArray array];
for (NSDictionary<NSString *, id> *crashLog in crashLogs) {
NSArray<NSString *> *callstack = crashLog[kFBSDKCallstack];
NSData *data = [self loadLibData:crashLog];
if (!data) {
continue;
}
NSDictionary<NSString *, id> *methodMapping = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:nil];
NSArray<NSString *> *symbolicatedCallstack = [FBSDKLibAnalyzer symbolicateCallstack:callstack methodMapping:methodMapping];
NSMutableDictionary<NSString *, id> *symbolicatedCrashLog = [NSMutableDictionary dictionaryWithDictionary:crashLog];
if (symbolicatedCallstack) {
[symbolicatedCrashLog setObject:symbolicatedCallstack forKey:kFBSDKCallstack];
[symbolicatedCrashLog removeObjectForKey:kFBSDKMappingTableIdentifier];
[processedCrashLogs addObject:symbolicatedCrashLog];
}
}
return processedCrashLogs;
}
+ (NSArray<NSDictionary<NSString *, id> *> *)loadCrashLogs
{
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:NULL];
NSArray<NSString *> *fileNames = [[self getCrashLogFileNames:files] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2){
return [obj2 compare:obj1];
}];
NSMutableArray<NSDictionary<NSString *, id> *> *crashLogArray = [NSMutableArray array];
for (NSUInteger i = 0; i < MIN(fileNames.count, FBSDK_MAX_CRASH_LOGS); i++) {
NSData *data = [self loadCrashLog:fileNames[i]];
if (!data) {
continue;
}
NSDictionary<NSString *, id>* crashLog = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:nil];
if (crashLog) {
[crashLogArray addObject:crashLog];
}
}
return [crashLogArray copy];
}
+ (nullable NSData *)loadCrashLog:(NSString *)fileName
{
return [NSData dataWithContentsOfFile:[directoryPath stringByAppendingPathComponent:fileName] options:NSDataReadingMappedIfSafe error:nil];
}
+ (void)clearCrashReportFiles
{
NSArray<NSString *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil];
for (NSUInteger i = 0; i < files.count; i++) {
// remove all crash related files except for the current mapping table
if ([files[i] hasPrefix:@"crash_"] && ![files[i] containsString:mappingTableIdentifier]) {
[[NSFileManager defaultManager] removeItemAtPath:[directoryPath stringByAppendingPathComponent:files[i]] error:nil];
}
}
}
+ (NSArray<NSString *> *)getCrashLogFileNames:(NSArray<NSString *> *)files
{
NSMutableArray<NSString *> *fileNames = [NSMutableArray array];
for (NSString *fileName in files) {
if ([fileName hasPrefix:@"crash_log_"] && [fileName hasSuffix:@".json"]) {
[fileNames addObject:fileName];
}
}
return fileNames;
}
+ (void)saveCrashLog:(NSDictionary<NSString *, id> *)crashLog
{
NSMutableDictionary<NSString *, id> *completeCrashLog = [NSMutableDictionary dictionaryWithDictionary:crashLog];
NSString *currentTimestamp = [NSString stringWithFormat:@"%.0lf", [[NSDate date] timeIntervalSince1970]];
[completeCrashLog setObject:currentTimestamp forKey:kFBSDKCrashTimestamp];
[completeCrashLog setObject:mappingTableIdentifier forKey:kFBSDKMappingTableIdentifier];
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *version = [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSString *build = [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"];
[completeCrashLog setObject:[NSString stringWithFormat:@"%@(%@)", version, build] forKey:kFBSDKAppVersion];
struct utsname systemInfo;
uname(&systemInfo);
[completeCrashLog setObject:@(systemInfo.machine) forKey:kFBSDKDeviceModel];
[completeCrashLog setObject:[UIDevice currentDevice].systemVersion forKey:kFBSDKDeviceOSVersion];
NSData *data = [NSJSONSerialization dataWithJSONObject:completeCrashLog options:0 error:nil];
[data writeToFile:[self getPathToCrashFile:currentTimestamp]
atomically:YES];
}
+ (void)generateMethodMapping:(id<FBSDKCrashObserving>)observer
{
if (observer.prefixes.count == 0) {
return;
}
[[NSUserDefaults standardUserDefaults] setObject:mappingTableIdentifier forKey:kFBSDKMappingTableIdentifier];
NSDictionary<NSString *, NSString *> *methodMapping = [FBSDKLibAnalyzer getMethodsTable:observer.prefixes
frameworks:observer.frameworks];
if (methodMapping.count > 0){
NSData *data = [NSJSONSerialization dataWithJSONObject:methodMapping options:0 error:nil];
[data writeToFile:[self getPathToLibDataFile:mappingTableIdentifier]
atomically:YES];
}
}
+ (nullable NSData *)loadLibData:(NSDictionary<NSString *, id> *)crashLog
{
NSString *identifier = [crashLog objectForKey:kFBSDKMappingTableIdentifier];
return [NSData dataWithContentsOfFile:[self getPathToLibDataFile:identifier] options:NSDataReadingMappedIfSafe error:nil];
}
+ (NSString *)getPathToCrashFile:(NSString *)timestamp
{
return [directoryPath stringByAppendingPathComponent:
[NSString stringWithFormat:@"crash_log_%@.json", timestamp]];
}
+ (NSString *)getPathToLibDataFile:(NSString *)identifier
{
return [directoryPath stringByAppendingPathComponent:
[NSString stringWithFormat:@"crash_lib_data_%@.json", identifier]];
}
+ (BOOL)isSafeToGenerateMapping
{
#if TARGET_OS_SIMULATOR
return YES;
#else
NSString *identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kFBSDKMappingTableIdentifier];
//first app start
if (!identifier) {
return YES;
}
return [[NSFileManager defaultManager] fileExistsAtPath:[self getPathToLibDataFile:identifier]];
#endif
}
+ (NSString *)getFBSDKVersion
{
return FBSDK_VERSION_STRING;
}
@end

View File

@ -0,0 +1,34 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol FBSDKCrashObserving <NSObject>
@property (nonatomic, copy) NSArray<NSString *> *prefixes;
@property (nonatomic, copy, nullable) NSArray<NSString *> *frameworks;
@optional
- (void)didReceiveCrashLogs:(NSArray<NSDictionary<NSString *, id> *> *)crashLogs;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,32 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKLibAnalyzer : NSObject
+ (NSDictionary<NSString *, NSString *> *)getMethodsTable:(NSArray<NSString *> *)prefixes
frameworks:(NSArray<NSString *> * _Nullable)frameworks;
+ (nullable NSArray<NSString *> *)symbolicateCallstack:(NSArray<NSString *> *)callstack
methodMapping:(NSDictionary<NSString *,id> *)methodMapping;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,227 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKLibAnalyzer.h"
#import <objc/runtime.h>
@implementation FBSDKLibAnalyzer
static NSMutableDictionary<NSString *, NSString *> *_methodMapping;
+ (void)initialize
{
_methodMapping = [NSMutableDictionary dictionary];
}
+ (NSDictionary<NSString *, NSString *> *)getMethodsTable:(NSArray<NSString *> *)prefixes
frameworks:(NSArray<NSString *> *)frameworks
{
NSArray<NSString *> *allClasses = [self getClassNames:prefixes frameworks:frameworks];
for (NSString *className in allClasses) {
Class class = NSClassFromString(className);
if (class) {
[self addClass:class isClassMethod:NO];
[self addClass:object_getClass(class) isClassMethod:YES];
}
}
@synchronized (_methodMapping) {
return [_methodMapping copy];
}
}
#pragma mark - private methods
+ (NSArray<NSString *> *)getClassNames:(NSArray<NSString *> *)prefixes
frameworks:(NSArray<NSString *> *)frameworks
{
NSMutableArray<NSString *> *classNames = [NSMutableArray new];
// from main bundle
[classNames addObjectsFromArray:[self getClassesFrom:[[NSBundle mainBundle] executablePath]
prefixes:prefixes]];
// from dynamic libraries
if (frameworks.count > 0) {
unsigned int count = 0;
const char **images = objc_copyImageNames(&count);
for (int i = 0; i < count; i++) {
NSString *image = [NSString stringWithUTF8String:images[i]];
for (NSString *framework in frameworks) {
if ([image containsString:framework]) {
[classNames addObjectsFromArray:[self getClassesFrom:image
prefixes:nil]];
}
}
}
free(images);
}
return [classNames copy];
}
+ (NSArray<NSString *> *)getClassesFrom:(NSString *)image
prefixes:(NSArray<NSString *> *)prefixes
{
NSMutableArray<NSString *> *classNames = [NSMutableArray array];
unsigned int count = 0;
const char **classes = objc_copyClassNamesForImage([image UTF8String], &count);
for (unsigned int i = 0; i < count; i++){
NSString *className = [NSString stringWithUTF8String:classes[i]];
if (prefixes.count > 0) {
for (NSString *prefix in prefixes) {
if ([className hasPrefix:prefix]) {
[classNames addObject:className];
break;
}
}
} else {
[classNames addObject:className];
}
}
free(classes);
return [classNames copy];
}
+ (void)addClass:(Class)class
isClassMethod:(BOOL)isClassMethod
{
unsigned int methodsCount = 0;
Method *methods = class_copyMethodList(class, &methodsCount);
NSString *methodType = isClassMethod ? @"+" : @"-";
for (unsigned int i = 0; i < methodsCount; i++) {
Method method = methods[i];
if (method) {
SEL selector = method_getName(method);
IMP methodImplementation = class_getMethodImplementation(class, selector);
NSString *methodAddress = [NSString stringWithFormat:@"0x%010lx", (unsigned long)methodImplementation];
NSString *methodName = [NSString stringWithFormat:@"%@[%@ %@]",
methodType,
NSStringFromClass(class),
NSStringFromSelector(selector)];
if (methodAddress && methodName) {
@synchronized (_methodMapping) {
[_methodMapping setObject:methodName forKey:methodAddress];
}
}
}
}
free(methods);
}
+ (NSArray<NSString *> *)symbolicateCallstack:(NSArray<NSString *> *)callstack
methodMapping:(NSDictionary<NSString *,id> *)methodMapping
{
if (!callstack || !methodMapping) {
return nil;
}
NSArray<NSString *> *sortedAllAddress = [methodMapping.allKeys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
BOOL containsFBSDKFunction = NO;
NSInteger nonSDKMethodCount = 0;
NSMutableArray<NSString *> *symbolicatedCallstack = [NSMutableArray array];
for (NSUInteger i = 0; i < callstack.count; i++){
NSString *rawAddress = [self getAddress:callstack[i]];
NSString *addressString = [NSString stringWithFormat:@"0x%@",[rawAddress substringWithRange:NSMakeRange(rawAddress.length - 10, 10)]];
NSString *methodAddress = [self searchMethod:addressString sortedAllAddress:sortedAllAddress];
if (methodAddress) {
containsFBSDKFunction = YES;
nonSDKMethodCount == 0 ?: [symbolicatedCallstack addObject:[NSString stringWithFormat:@"(%ld DEV METHODS)", (long)nonSDKMethodCount]];
nonSDKMethodCount = 0;
NSString *methodName = [methodMapping objectForKey:methodAddress];
// filter out cxx_destruct
if ([methodName containsString:@".cxx_destruct"]) {
return nil;
}
[symbolicatedCallstack addObject:[NSString stringWithFormat:@"%@%@", methodName, [self getOffset:addressString secondString:methodAddress]]];
} else {
nonSDKMethodCount++;
}
}
nonSDKMethodCount == 0 ?: [symbolicatedCallstack addObject:[NSString stringWithFormat:@"(%ld DEV METHODS)", (long)nonSDKMethodCount]];
return containsFBSDKFunction ? symbolicatedCallstack : nil;
}
+ (NSString *)getAddress:(NSString *)callstackEntry
{
NSArray<NSString *> *components = [callstackEntry componentsSeparatedByString:@" "];
for (NSString *component in components) {
if ([component containsString:@"0x"]) {
return component;
}
}
return nil;
}
+ (NSString *)getOffset:(NSString *)firstString
secondString:(NSString *)secondString
{
if (!firstString || !secondString) {
return nil;
}
unsigned long long first = 0, second = 0;
NSScanner *scanner = [NSScanner scannerWithString:firstString];
[scanner scanHexLongLong:&first];
scanner = [NSScanner scannerWithString:secondString];
[scanner scanHexLongLong:&second];
unsigned long long difference = first - second;
return [NSString stringWithFormat:@"+%llu", difference];
}
+ (NSString *)searchMethod:(NSString *)address
sortedAllAddress:(NSArray<NSString *> *)sortedAllAddress
{
if (0 == sortedAllAddress.count) {
return nil;
}
NSString *lowestAddress = sortedAllAddress[0];
NSString *highestAddress = sortedAllAddress[sortedAllAddress.count - 1];
if ([address compare:lowestAddress] == NSOrderedAscending || [address compare:highestAddress] == NSOrderedDescending) {
return nil;
}
if ([address compare:lowestAddress] == NSOrderedSame) {
return lowestAddress;
}
if ([address compare:highestAddress] == NSOrderedSame) {
return highestAddress;
}
NSUInteger index = [sortedAllAddress indexOfObject:address
inSortedRange:NSMakeRange(0, sortedAllAddress.count - 1)
options:NSBinarySearchingInsertionIndex
usingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
return sortedAllAddress[index - 1];
}
@end

View File

@ -0,0 +1,135 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Describes the callback for appLinkFromURLInBackground.
@param object the FBSDKAppLink representing the deferred App Link
@param stop the error during the request, if any
*/
typedef id _Nullable (^FBSDKInvalidObjectHandler)(id object, BOOL *stop)
NS_SWIFT_NAME(InvalidObjectHandler);
@interface FBSDKBasicUtility : NSObject
/**
Converts an object into a JSON string.
@param object The object to convert to JSON.
@param errorRef If an error occurs, upon return contains an NSError object that describes the problem.
@param invalidObjectHandler Handles objects that are invalid, returning a replacement value or nil to ignore.
@return A JSON string or nil if the object cannot be converted to JSON.
*/
+ (nullable NSString *)JSONStringForObject:(id)object
error:(NSError *__autoreleasing *)errorRef
invalidObjectHandler:(nullable FBSDKInvalidObjectHandler)invalidObjectHandler;
/**
Sets an object for a key in a dictionary if it is not nil.
@param dictionary The dictionary to set the value for.
@param object The value to set.
@param key The key to set the value for.
*/
+ (void)dictionary:(NSMutableDictionary<NSString *, id> *)dictionary
setObject:(nullable id)object
forKey:(nullable id<NSCopying>)key;
/**
Sets an object for a key in a dictionary if it is not nil.
@param dictionary The dictionary to set the value for.
@param object The value to set after serializing to JSON.
@param key The key to set the value for.
@param errorRef If an error occurs, upon return contains an NSError object that describes the problem.
@return NO if an error occurred while serializing the object, otherwise YES.
*/
+ (BOOL)dictionary:(NSMutableDictionary<id, id> *)dictionary
setJSONStringForObject:(id)object
forKey:(id<NSCopying>)key
error:(NSError *__autoreleasing *)errorRef;
/**
Adds an object to an array if it is not nil.
@param array The array to add the object to.
@param object The object to add to the array.
*/
+ (void)array:(NSMutableArray *)array addObject:(nullable id)object;
/**
Converts a JSON string into an object
@param string The JSON string to convert.
@param errorRef If an error occurs, upon return contains an NSError object that describes the problem.
@return An NSDictionary, NSArray, NSString or NSNumber containing the object representation, or nil if the string
cannot be converted.
*/
+ (nullable id)objectForJSONString:(NSString *)string error:(NSError *__autoreleasing *)errorRef;
/**
Constructs a query string from a dictionary.
@param dictionary The dictionary with key/value pairs for the query string.
@param errorRef If an error occurs, upon return contains an NSError object that describes the problem.
@param invalidObjectHandler Handles objects that are invalid, returning a replacement value or nil to ignore.
@return Query string representation of the parameters.
*/
+ (NSString *)queryStringWithDictionary:(NSDictionary<NSString *, id> *)dictionary
error:(NSError *__autoreleasing *)errorRef
invalidObjectHandler:(nullable FBSDKInvalidObjectHandler)invalidObjectHandler;
/**
Converts simple value types to the string equivalent for serializing to a request query or body.
@param value The value to be converted.
@return The value that may have been converted if able (otherwise the input param).
*/
+ (id)convertRequestValue:(id)value;
/**
Encodes a value for an URL.
@param value The value to encode.
@return The encoded value.
*/
+ (NSString *)URLEncode:(NSString *)value;
/**
Parses a query string into a dictionary.
@param queryString The query string value.
@return A dictionary with the key/value pairs.
*/
+ (NSDictionary<NSString *, NSString *> *)dictionaryWithQueryString:(NSString *)queryString;
/**
Decodes a value from an URL.
@param value The value to decode.
@return The decoded value.
*/
+ (NSString *)URLDecode:(NSString *)value;
/**
Gzip data with default compression level if possible.
@param data The raw data.
@return nil if unable to gzip the data, otherwise gzipped data.
*/
+ (nullable NSData *)gzip:(NSData *)data;
+ (NSString *)anonymousID;
+ (NSString *)persistenceFilePath:(NSString *)filename;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,341 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKBasicUtility.h"
#import <zlib.h>
#import "FBSDKTypeUtility.h"
#define kChunkSize 1024
static NSString *const FBSDK_BASICUTILITY_ANONYMOUSIDFILENAME = @"com-facebook-sdk-PersistedAnonymousID.json";
static NSString *const FBSDK_BASICUTILITY_ANONYMOUSID_KEY = @"anon_id";
@protocol BASIC_FBSDKError
+ (NSError *)invalidArgumentErrorWithName:(NSString *)name value:(id)value message:(NSString *)message;
@end
@implementation FBSDKBasicUtility
+ (NSString *)JSONStringForObject:(id)object
error:(NSError *__autoreleasing *)errorRef
invalidObjectHandler:(FBSDKInvalidObjectHandler)invalidObjectHandler
{
if (invalidObjectHandler || ![NSJSONSerialization isValidJSONObject:object]) {
object = [self _convertObjectToJSONObject:object invalidObjectHandler:invalidObjectHandler stop:NULL];
if (![NSJSONSerialization isValidJSONObject:object]) {
if (errorRef != NULL) {
Class FBSDKErrorClass = NSClassFromString(@"FBSDKError");
if ([FBSDKErrorClass respondsToSelector:@selector(invalidArgumentErrorWithName:value:message:)]) {
*errorRef = [FBSDKErrorClass invalidArgumentErrorWithName:@"object"
value:object
message:@"Invalid object for JSON serialization."];
}
}
return nil;
}
}
NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:errorRef];
if (!data) {
return nil;
}
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
+ (BOOL)dictionary:(NSMutableDictionary<NSString *, id> *)dictionary
setJSONStringForObject:(id)object
forKey:(id<NSCopying>)key
error:(NSError *__autoreleasing *)errorRef
{
if (!object || !key) {
return YES;
}
NSString *JSONString = [self JSONStringForObject:object error:errorRef invalidObjectHandler:NULL];
if (!JSONString) {
return NO;
}
[self dictionary:dictionary setObject:JSONString forKey:key];
return YES;
}
+ (id)_convertObjectToJSONObject:(id)object
invalidObjectHandler:(FBSDKInvalidObjectHandler)invalidObjectHandler
stop:(BOOL *)stopRef
{
__block BOOL stop = NO;
if ([object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNumber class]]) {
// good to go, keep the object
} else if ([object isKindOfClass:[NSURL class]]) {
object = ((NSURL *)object).absoluteString;
} else if ([object isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary<NSString *, id> *dictionary = [[NSMutableDictionary alloc] init];
[(NSDictionary<id, id> *)object enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *dictionaryStop) {
[self dictionary:dictionary
setObject:[self _convertObjectToJSONObject:obj invalidObjectHandler:invalidObjectHandler stop:&stop]
forKey:[FBSDKTypeUtility stringValue:key]];
if (stop) {
*dictionaryStop = YES;
}
}];
object = dictionary;
} else if ([object isKindOfClass:[NSArray class]]) {
NSMutableArray<id> *array = [[NSMutableArray alloc] init];
for (id obj in (NSArray *)object) {
id convertedObj = [self _convertObjectToJSONObject:obj invalidObjectHandler:invalidObjectHandler stop:&stop];
[self array:array addObject:convertedObj];
if (stop) {
break;
}
}
object = array;
} else {
object = invalidObjectHandler(object, stopRef);
}
if (stopRef != NULL) {
*stopRef = stop;
}
return object;
}
+ (void)dictionary:(NSMutableDictionary<NSString *, id> *)dictionary setObject:(id)object forKey:(id<NSCopying>)key
{
if (object && key) {
dictionary[key] = object;
}
}
+ (void)array:(NSMutableArray *)array addObject:(id)object
{
if (object) {
[array addObject:object];
}
}
+ (id)objectForJSONString:(NSString *)string error:(NSError *__autoreleasing *)errorRef
{
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
if (!data) {
if (errorRef != NULL) {
*errorRef = nil;
}
return nil;
}
return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:errorRef];
}
+ (NSString *)queryStringWithDictionary:(NSDictionary<id, id> *)dictionary
error:(NSError *__autoreleasing *)errorRef
invalidObjectHandler:(FBSDKInvalidObjectHandler)invalidObjectHandler
{
NSMutableString *queryString = [[NSMutableString alloc] init];
__block BOOL hasParameters = NO;
if (dictionary) {
NSMutableArray<NSString *> *keys = [dictionary.allKeys mutableCopy];
// remove non-string keys, as they are not valid
[keys filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary<id, id> *bindings) {
return [evaluatedObject isKindOfClass:[NSString class]];
}]];
// sort the keys so that the query string order is deterministic
[keys sortUsingSelector:@selector(compare:)];
BOOL stop = NO;
for (NSString *key in keys) {
id value = [self convertRequestValue:dictionary[key]];
if ([value isKindOfClass:[NSString class]]) {
value = [self URLEncode:value];
}
if (invalidObjectHandler && ![value isKindOfClass:[NSString class]]) {
value = invalidObjectHandler(value, &stop);
if (stop) {
break;
}
}
if (value) {
if (hasParameters) {
[queryString appendString:@"&"];
}
[queryString appendFormat:@"%@=%@", key, value];
hasParameters = YES;
}
}
}
if (errorRef != NULL) {
*errorRef = nil;
}
return (queryString.length ? [queryString copy] : nil);
}
+ (id)convertRequestValue:(id)value
{
if ([value isKindOfClass:[NSNumber class]]) {
value = ((NSNumber *)value).stringValue;
} else if ([value isKindOfClass:[NSURL class]]) {
value = ((NSURL *)value).absoluteString;
}
return value;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ (NSString *)URLEncode:(NSString *)value
{
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)value,
NULL, // characters to leave unescaped
CFSTR(":!*();@/&?+$,='"),
kCFStringEncodingUTF8);
}
#pragma clang diagnostic pop
+ (NSDictionary<NSString *, NSString *> *)dictionaryWithQueryString:(NSString *)queryString
{
NSMutableDictionary<NSString *, NSString *> *result = [[NSMutableDictionary alloc] init];
NSArray<NSString *> *parts = [queryString componentsSeparatedByString:@"&"];
for (NSString *part in parts) {
if (part.length == 0) {
continue;
}
NSRange index = [part rangeOfString:@"="];
NSString *key;
NSString *value;
if (index.location == NSNotFound) {
key = part;
value = @"";
} else {
key = [part substringToIndex:index.location];
value = [part substringFromIndex:index.location + index.length];
}
key = [self URLDecode:key];
value = [self URLDecode:value];
if (key && value) {
result[key] = value;
}
}
return result;
}
+ (NSString *)URLDecode:(NSString *)value
{
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
#pragma clang diagnostic pop
return value;
}
+ (NSData *)gzip:(NSData *)data
{
const void *bytes = data.bytes;
const NSUInteger length = data.length;
if (!bytes || !length) {
return nil;
}
#if defined(__LP64__) && __LP64__
if (length > UINT_MAX) {
return nil;
}
#endif
// initialze stream
z_stream stream;
bzero(&stream, sizeof(z_stream));
if (deflateInit2(&stream, -1, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
return nil;
}
stream.avail_in = (uint)length;
stream.next_in = (Bytef *)bytes;
int retCode;
NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)];
unsigned char output[kChunkSize];
do {
stream.avail_out = kChunkSize;
stream.next_out = output;
retCode = deflate(&stream, Z_FINISH);
if (retCode != Z_OK && retCode != Z_STREAM_END) {
deflateEnd(&stream);
return nil;
}
unsigned size = kChunkSize - stream.avail_out;
if (size > 0) {
[result appendBytes:output length:size];
}
} while (retCode == Z_OK);
deflateEnd(&stream);
return result;
}
+ (NSString *)anonymousID
{
// Grab previously written anonymous ID and, if none have been generated, create and
// persist a new one which will remain associated with this app.
NSString *result = [[self class] retrievePersistedAnonymousID];
if (!result) {
// Generate a new anonymous ID. Create as a UUID, but then prepend the fairly
// arbitrary 'XZ' to the front so it's easily distinguishable from IDFA's which
// will only contain hex.
result = [NSString stringWithFormat:@"XZ%@", [NSUUID UUID].UUIDString];
[self persistAnonymousID:result];
}
return result;
}
+ (NSString *)retrievePersistedAnonymousID
{
NSString *file = [[self class] persistenceFilePath:FBSDK_BASICUTILITY_ANONYMOUSIDFILENAME];
NSString *content = [[NSString alloc] initWithContentsOfFile:file
encoding:NSASCIIStringEncoding
error:nil];
NSDictionary<NSString *, id> *results = [FBSDKBasicUtility objectForJSONString:content error:NULL];
return results[FBSDK_BASICUTILITY_ANONYMOUSID_KEY];
}
+ (NSString *)persistenceFilePath:(NSString *)filename
{
NSSearchPathDirectory directory = NSLibraryDirectory;
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
NSString *docDirectory = paths[0];
return [docDirectory stringByAppendingPathComponent:filename];
}
+ (void)persistAnonymousID:(NSString *)anonymousID
{
NSDictionary<NSString *, NSString *> *data = @{ FBSDK_BASICUTILITY_ANONYMOUSID_KEY : anonymousID };
NSString *content = [self JSONStringForObject:data error:NULL invalidObjectHandler:NULL];
[content writeToFile:[[self class] persistenceFilePath:FBSDK_BASICUTILITY_ANONYMOUSIDFILENAME]
atomically:YES
encoding:NSASCIIStringEncoding
error:nil];
}
@end

View File

@ -0,0 +1,37 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_SWIFT_NAME(TypeUtility)
@interface FBSDKTypeUtility : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (NSArray *)arrayValue:(id)object;
+ (BOOL)boolValue:(id)object;
+ (NSDictionary *)dictionaryValue:(id)object;
+ (NSInteger)integerValue:(id)object;
+ (id)objectValue:(id)object;
+ (NSString *)stringValue:(id)object;
+ (NSTimeInterval)timeIntervalValue:(id)object;
+ (NSUInteger)unsignedIntegerValue:(id)object;
+ (NSURL *)URLValue:(id)object;
@end

View File

@ -0,0 +1,121 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKTypeUtility.h"
@implementation FBSDKTypeUtility
#pragma mark - Class Methods
+ (NSArray *)arrayValue:(id)object
{
return (NSArray *)[self _objectValue:object ofClass:[NSArray class]];
}
+ (BOOL)boolValue:(id)object
{
if ([object isKindOfClass:[NSNumber class]]) {
// @0 or @NO returns NO, otherwise YES
return ((NSNumber *)object).boolValue;
} else if ([object isKindOfClass:[NSString class]]) {
// Returns YES on encountering one of "Y", "y", "T", "t", or a digit 1-9, otherwise NO
return ((NSString *)object).boolValue;
} else {
return ([self objectValue:object] != nil);
}
}
+ (NSDictionary *)dictionaryValue:(id)object
{
return (NSDictionary *)[self _objectValue:object ofClass:[NSDictionary class]];
}
+ (NSInteger)integerValue:(id)object
{
if ([object isKindOfClass:[NSNumber class]]) {
return ((NSNumber *)object).integerValue;
} else if ([object isKindOfClass:[NSString class]]) {
return ((NSString *)object).integerValue;
} else {
return 0;
}
}
+ (id)objectValue:(id)object
{
return ([object isKindOfClass:[NSNull class]] ? nil : object);
}
+ (NSString *)stringValue:(id)object
{
if ([object isKindOfClass:[NSString class]]) {
return (NSString *)object;
} else if ([object isKindOfClass:[NSNumber class]]) {
return ((NSNumber *)object).stringValue;
} else if ([object isKindOfClass:[NSURL class]]) {
return ((NSURL *)object).absoluteString;
} else {
return nil;
}
}
+ (NSTimeInterval)timeIntervalValue:(id)object
{
if ([object isKindOfClass:[NSNumber class]]) {
return ((NSNumber *)object).doubleValue;
} else if ([object isKindOfClass:[NSString class]]) {
return ((NSString *)object).doubleValue;
} else {
return 0;
}
}
+ (NSUInteger)unsignedIntegerValue:(id)object
{
if ([object isKindOfClass:[NSNumber class]]) {
return ((NSNumber *)object).unsignedIntegerValue;
} else {
// there is no direct support for strings containing unsigned values > NSIntegerMax - not worth writing ourselves
// right now, so just cap unsigned values at NSIntegerMax until we have a need for larger
NSInteger integerValue = [self integerValue:object];
if (integerValue < 0) {
integerValue = 0;
}
return (NSUInteger)integerValue;
}
}
+ (NSURL *)URLValue:(id)object
{
if ([object isKindOfClass:[NSURL class]]) {
return (NSURL *)object;
} else if ([object isKindOfClass:[NSString class]]) {
return [NSURL URLWithString:(NSString *)object];
} else {
return nil;
}
}
#pragma mark - Helper Methods
+ (id)_objectValue:(id)object ofClass:(Class)expectedClass
{
return ([object isKindOfClass:expectedClass] ? object : nil);
}
@end

View File

@ -0,0 +1,45 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "FBSDKURLSessionTask.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKURLSession : NSObject
@property (atomic, strong, nullable) NSURLSession *session;
@property (nonatomic, weak, nullable) id<NSURLSessionDataDelegate> delegate;
@property (nonatomic, retain, nullable) NSOperationQueue *delegateQueue;
- (instancetype)initWithDelegate:(id<NSURLSessionDataDelegate>)delegate
delegateQueue:(NSOperationQueue *)delegateQueue;
- (void)executeURLRequest:(NSURLRequest *)request
completionHandler:(FBSDKURLSessionTaskBlock)handler;
- (void)updateSessionWithBlock:(dispatch_block_t)block;
- (void)invalidateAndCancel;
- (BOOL)valid;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,71 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKURLSession.h"
#import "FBSDKBasicUtility.h"
#import "FBSDKURLSessionTask.h"
@implementation FBSDKURLSession
- (instancetype)initWithDelegate:(id<NSURLSessionDataDelegate>)delegate
delegateQueue:(NSOperationQueue *)queue
{
if ((self = [super init])) {
self.delegate = delegate;
self.delegateQueue = queue;
}
return self;
}
- (void)executeURLRequest:(NSURLRequest *)request
completionHandler:(FBSDKURLSessionTaskBlock)handler
{
if (!self.valid) {
[self updateSessionWithBlock:^{
FBSDKURLSessionTask *task = [[FBSDKURLSessionTask alloc] initWithRequest:request fromSession:self.session completionHandler:handler];
[task start];
}];
} else {
FBSDKURLSessionTask *task = [[FBSDKURLSessionTask alloc] initWithRequest:request fromSession:self.session completionHandler:handler];
[task start];
}
}
- (void)updateSessionWithBlock:(dispatch_block_t)block
{
if (!self.valid) {
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:_delegate
delegateQueue:_delegateQueue];
}
block();
}
- (void)invalidateAndCancel
{
[self.session invalidateAndCancel];
self.session = nil;
}
- (BOOL)valid
{
return self.session != nil;
}
@end

View File

@ -0,0 +1,45 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
typedef void (^FBSDKURLSessionTaskBlock)(NSData *responseData,
NSURLResponse *response,
NSError *error)
NS_SWIFT_NAME(URLSessionTaskBlock);
NS_SWIFT_NAME(URLSessionTask)
@interface FBSDKURLSessionTask : NSObject
@property (nonatomic, strong) NSURLSessionTask *task;
@property (atomic, readonly) NSURLSessionTaskState state;
@property (nonatomic, strong, readonly) NSDate *requestStartDate;
@property (nonatomic, copy) FBSDKURLSessionTaskBlock handler;
@property (nonatomic, assign) uint64_t requestStartTime;
@property (nonatomic, assign) NSUInteger loggerSerialNumber;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithRequest:(NSURLRequest *)request
fromSession:(NSURLSession *)session
completionHandler:(FBSDKURLSessionTaskBlock)handler;
- (void)start;
- (void)cancel;
@end

View File

@ -0,0 +1,60 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKURLSessionTask.h"
@implementation FBSDKURLSessionTask
- (instancetype)init
{
if ((self = [super init])) {
_requestStartDate = [NSDate date];
}
return self;
}
- (instancetype)initWithRequest:(NSURLRequest *)request
fromSession:(NSURLSession *)session
completionHandler:(FBSDKURLSessionTaskBlock)handler
{
if ((self = [self init])) {
self.requestStartTime = (uint64_t)([self.requestStartDate timeIntervalSince1970] * 1000);
self.task = [session dataTaskWithRequest:request completionHandler:handler];
}
return self;
}
- (NSURLSessionTaskState)state
{
return self.task.state;
}
#pragma mark - Task State
- (void)start
{
[self.task resume];
}
- (void)cancel
{
[self.task cancel];
self.handler = nil;
}
@end

View File

@ -0,0 +1,274 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "FBSDKCopying.h"
#ifdef BUCK
#import <FBSDKCoreKit/FBSDKGraphRequestConnection.h>
#else
#import "FBSDKGraphRequestConnection.h"
#endif
NS_ASSUME_NONNULL_BEGIN
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/**
Notification indicating that the `currentAccessToken` has changed.
the userInfo dictionary of the notification will contain keys
`FBSDKAccessTokenChangeOldKey` and
`FBSDKAccessTokenChangeNewKey`.
*/
FOUNDATION_EXPORT NSNotificationName const FBSDKAccessTokenDidChangeNotification
NS_SWIFT_NAME(AccessTokenDidChange);
#else
/**
Notification indicating that the `currentAccessToken` has changed.
the userInfo dictionary of the notification will contain keys
`FBSDKAccessTokenChangeOldKey` and
`FBSDKAccessTokenChangeNewKey`.
*/
FOUNDATION_EXPORT NSString *const FBSDKAccessTokenDidChangeNotification
NS_SWIFT_NAME(AccessTokenDidChangeNotification);
#endif
/**
A key in the notification's userInfo that will be set
if and only if the user ID changed between the old and new tokens.
Token refreshes can occur automatically with the SDK
which do not change the user. If you're only interested in user
changes (such as logging out), you should check for the existence
of this key. The value is a NSNumber with a boolValue.
On a fresh start of the app where the SDK reads in the cached value
of an access token, this key will also exist since the access token
is moving from a null state (no user) to a non-null state (user).
*/
FOUNDATION_EXPORT NSString *const FBSDKAccessTokenDidChangeUserIDKey
NS_SWIFT_NAME(AccessTokenDidChangeUserIDKey);
/*
key in notification's userInfo object for getting the old token.
If there was no old token, the key will not be present.
*/
FOUNDATION_EXPORT NSString *const FBSDKAccessTokenChangeOldKey
NS_SWIFT_NAME(AccessTokenChangeOldKey);
/*
key in notification's userInfo object for getting the new token.
If there is no new token, the key will not be present.
*/
FOUNDATION_EXPORT NSString *const FBSDKAccessTokenChangeNewKey
NS_SWIFT_NAME(AccessTokenChangeNewKey);
/*
A key in the notification's userInfo that will be set
if and only if the token has expired.
*/
FOUNDATION_EXPORT NSString *const FBSDKAccessTokenDidExpireKey
NS_SWIFT_NAME(AccessTokenDidExpireKey);
/**
Represents an immutable access token for using Facebook services.
*/
NS_SWIFT_NAME(AccessToken)
@interface FBSDKAccessToken : NSObject<FBSDKCopying, NSSecureCoding>
/**
The "global" access token that represents the currently logged in user.
The `currentAccessToken` is a convenient representation of the token of the
current user and is used by other SDK components (like `FBSDKLoginManager`).
*/
@property (class, nonatomic, copy, nullable) FBSDKAccessToken *currentAccessToken;
/**
Returns YES if currentAccessToken is not nil AND currentAccessToken is not expired
*/
@property (class, nonatomic, assign, readonly, getter=isCurrentAccessTokenActive) BOOL currentAccessTokenIsActive;
/**
Returns the app ID.
*/
@property (nonatomic, copy, readonly) NSString *appID;
/**
Returns the expiration date for data access
*/
@property (nonatomic, copy, readonly) NSDate *dataAccessExpirationDate;
/**
Returns the known declined permissions.
*/
@property (nonatomic, copy, readonly) NSSet<NSString *> *declinedPermissions
NS_REFINED_FOR_SWIFT;
/**
Returns the known declined permissions.
*/
@property (nonatomic, copy, readonly) NSSet<NSString *> *expiredPermissions
NS_REFINED_FOR_SWIFT;
/**
Returns the expiration date.
*/
@property (nonatomic, copy, readonly) NSDate *expirationDate;
/**
Returns the known granted permissions.
*/
@property (nonatomic, copy, readonly) NSSet<NSString *> *permissions
NS_REFINED_FOR_SWIFT;
/**
Returns the date the token was last refreshed.
*/
@property (nonatomic, copy, readonly) NSDate *refreshDate;
/**
Returns the opaque token string.
*/
@property (nonatomic, copy, readonly) NSString *tokenString;
/**
Returns the user ID.
*/
@property (nonatomic, copy, readonly) NSString *userID;
/**
The graph domain where this access token is valid.
*/
@property (nonatomic, copy, readonly) NSString *graphDomain;
/**
Returns whether the access token is expired by checking its expirationDate property
*/
@property (readonly, assign, nonatomic, getter=isExpired) BOOL expired;
/**
Returns whether user data access is still active for the given access token
*/
@property (readonly, assign, nonatomic, getter=isDataAccessExpired) BOOL dataAccessExpired;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
Initializes a new instance.
@param tokenString the opaque token string.
@param permissions the granted permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param declinedPermissions the declined permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param expiredPermissions the expired permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param appID the app ID.
@param userID the user ID.
@param expirationDate the optional expiration date (defaults to distantFuture).
@param refreshDate the optional date the token was last refreshed (defaults to today).
@param dataAccessExpirationDate the date which data access will expire for the given user
(defaults to distantFuture).
This initializer should only be used for advanced apps that
manage tokens explicitly. Typical login flows only need to use `FBSDKLoginManager`
along with `+currentAccessToken`.
*/
- (instancetype)initWithTokenString:(NSString *)tokenString
permissions:(NSArray<NSString *> *)permissions
declinedPermissions:(NSArray<NSString *> *)declinedPermissions
expiredPermissions:(NSArray<NSString *> *)expiredPermissions
appID:(NSString *)appID
userID:(NSString *)userID
expirationDate:(nullable NSDate *)expirationDate
refreshDate:(nullable NSDate *)refreshDate
dataAccessExpirationDate:(nullable NSDate *)dataAccessExpirationDate
NS_DESIGNATED_INITIALIZER;
/**
Convenience initializer.
@param tokenString the opaque token string.
@param permissions the granted permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param declinedPermissions the declined permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param expiredPermissions the expired permissions. Note this is converted to NSSet and is only
an NSArray for the convenience of literal syntax.
@param appID the app ID.
@param userID the user ID.
@param expirationDate the optional expiration date (defaults to distantFuture).
@param refreshDate the optional date the token was last refreshed (defaults to today).
@param dataAccessExpirationDate the date which data access will expire for the given user
(defaults to distantFuture).
@param graphDomain the domain this access token can be used in.
This initializer should only be used for advanced apps that
manage tokens explicitly. Typical login flows only need to use `FBSDKLoginManager`
along with `+currentAccessToken`.
*/
- (instancetype)initWithTokenString:(NSString *)tokenString
permissions:(NSArray<NSString *> *)permissions
declinedPermissions:(NSArray<NSString *> *)declinedPermissions
expiredPermissions:(NSArray<NSString *> *)expiredPermissions
appID:(NSString *)appID
userID:(NSString *)userID
expirationDate:(nullable NSDate *)expirationDate
refreshDate:(nullable NSDate *)refreshDate
dataAccessExpirationDate:(nullable NSDate *)dataAccessExpirationDate
graphDomain:(nullable NSString *)graphDomain;
/**
Convenience getter to determine if a permission has been granted
@param permission The permission to check.
*/
- (BOOL)hasGranted:(NSString *)permission
NS_SWIFT_NAME(hasGranted(permission:));
/**
Compares the receiver to another FBSDKAccessToken
@param token The other token
@return YES if the receiver's values are equal to the other token's values; otherwise NO
*/
- (BOOL)isEqualToAccessToken:(FBSDKAccessToken *)token;
/**
Refresh the current access token's permission state and extend the token's expiration date,
if possible.
@param completionHandler an optional callback handler that can surface any errors related to permission refreshing.
On a successful refresh, the currentAccessToken will be updated so you typically only need to
observe the `FBSDKAccessTokenDidChangeNotification` notification.
If a token is already expired, it cannot be refreshed.
*/
+ (void)refreshCurrentAccessToken:(nullable FBSDKGraphRequestBlock)completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,277 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKAccessToken.h"
#import "FBSDKGraphRequestPiggybackManager.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKMath.h"
#import "FBSDKSettings+Internal.h"
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
NSNotificationName const FBSDKAccessTokenDidChangeNotification = @"com.facebook.sdk.FBSDKAccessTokenData.FBSDKAccessTokenDidChangeNotification";
#else
NSString *const FBSDKAccessTokenDidChangeNotification = @"com.facebook.sdk.FBSDKAccessTokenData.FBSDKAccessTokenDidChangeNotification";
#endif
NSString *const FBSDKAccessTokenDidChangeUserIDKey = @"FBSDKAccessTokenDidChangeUserIDKey";
NSString *const FBSDKAccessTokenChangeNewKey = @"FBSDKAccessToken";
NSString *const FBSDKAccessTokenChangeOldKey = @"FBSDKAccessTokenOld";
NSString *const FBSDKAccessTokenDidExpireKey = @"FBSDKAccessTokenDidExpireKey";
static FBSDKAccessToken *g_currentAccessToken;
#define FBSDK_ACCESSTOKEN_TOKENSTRING_KEY @"tokenString"
#define FBSDK_ACCESSTOKEN_PERMISSIONS_KEY @"permissions"
#define FBSDK_ACCESSTOKEN_DECLINEDPERMISSIONS_KEY @"declinedPermissions"
#define FBSDK_ACCESSTOKEN_EXPIREDPERMISSIONS_KEY @"expiredPermissions"
#define FBSDK_ACCESSTOKEN_APPID_KEY @"appID"
#define FBSDK_ACCESSTOKEN_USERID_KEY @"userID"
#define FBSDK_ACCESSTOKEN_REFRESHDATE_KEY @"refreshDate"
#define FBSDK_ACCESSTOKEN_EXPIRATIONDATE_KEY @"expirationDate"
#define FBSDK_ACCESSTOKEN_DATA_EXPIRATIONDATE_KEY @"dataAccessExpirationDate"
#define FBSDK_ACCESSTOKEN_GRAPH_DOMAIN_KEY @"graphDomain"
@implementation FBSDKAccessToken
- (instancetype)initWithTokenString:(NSString *)tokenString
permissions:(NSArray *)permissions
declinedPermissions:(NSArray *)declinedPermissions
expiredPermissions:(NSArray *)expiredPermissions
appID:(NSString *)appID
userID:(NSString *)userID
expirationDate:(NSDate *)expirationDate
refreshDate:(NSDate *)refreshDate
dataAccessExpirationDate:(NSDate *)dataAccessExpirationDate
{
if ((self = [super init])) {
_tokenString = [tokenString copy];
_permissions = [NSSet setWithArray:permissions];
_declinedPermissions = [NSSet setWithArray:declinedPermissions];
_expiredPermissions = [NSSet setWithArray:expiredPermissions];
_appID = [appID copy];
_userID = [userID copy];
_expirationDate = [expirationDate copy] ?: [NSDate distantFuture];
_refreshDate = [refreshDate copy] ?: [NSDate date];
_dataAccessExpirationDate = [dataAccessExpirationDate copy] ?: [NSDate distantFuture];
}
return self;
}
- (instancetype)initWithTokenString:(NSString *)tokenString
permissions:(NSArray<NSString *> *)permissions
declinedPermissions:(NSArray<NSString *> *)declinedPermissions
expiredPermissions:(NSArray<NSString *> *)expiredPermissions
appID:(NSString *)appID
userID:(NSString *)userID
expirationDate:(NSDate *)expirationDate
refreshDate:(NSDate *)refreshDate
dataAccessExpirationDate:(NSDate *)dataAccessExpirationDate
graphDomain:(NSString *)graphDomain
{
FBSDKAccessToken *accessToken =
[self
initWithTokenString:tokenString
permissions:permissions
declinedPermissions:declinedPermissions
expiredPermissions:expiredPermissions
appID:appID
userID:userID
expirationDate:expirationDate
refreshDate:refreshDate
dataAccessExpirationDate:dataAccessExpirationDate];
if (accessToken != nil) {
accessToken->_graphDomain = [graphDomain copy];
}
return accessToken;
}
- (BOOL)hasGranted:(NSString *)permission
{
return [self.permissions containsObject:permission];
}
- (BOOL)isDataAccessExpired
{
return [self.dataAccessExpirationDate compare:NSDate.date] == NSOrderedAscending;
}
- (BOOL)isExpired
{
return [self.expirationDate compare:NSDate.date] == NSOrderedAscending;
}
+ (FBSDKAccessToken *)currentAccessToken
{
return g_currentAccessToken;
}
+ (void)setCurrentAccessToken:(FBSDKAccessToken *)token
{
if (token != g_currentAccessToken) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[FBSDKBasicUtility dictionary:userInfo setObject:token forKey:FBSDKAccessTokenChangeNewKey];
[FBSDKBasicUtility dictionary:userInfo setObject:g_currentAccessToken forKey:FBSDKAccessTokenChangeOldKey];
// We set this flag also when the current Access Token was not valid, since there might be legacy code relying on it
if (![g_currentAccessToken.userID isEqualToString:token.userID] || !self.isCurrentAccessTokenActive) {
userInfo[FBSDKAccessTokenDidChangeUserIDKey] = @YES;
}
g_currentAccessToken = token;
// Only need to keep current session in web view for the case when token is current
// When token is abandoned cookies must to be cleaned up immediately
if (token == nil) {
[FBSDKInternalUtility deleteFacebookCookies];
}
[FBSDKSettings accessTokenCache].accessToken = token;
[[NSNotificationCenter defaultCenter] postNotificationName:FBSDKAccessTokenDidChangeNotification
object:[self class]
userInfo:userInfo];
}
}
+ (BOOL)isCurrentAccessTokenActive
{
FBSDKAccessToken *currentAccessToken = [self currentAccessToken];
return currentAccessToken != nil && !currentAccessToken.isExpired;
}
+ (void)refreshCurrentAccessToken:(FBSDKGraphRequestBlock)completionHandler
{
if ([FBSDKAccessToken currentAccessToken]) {
FBSDKGraphRequestConnection *connection = [[FBSDKGraphRequestConnection alloc] init];
[FBSDKGraphRequestPiggybackManager addRefreshPiggyback:connection permissionHandler:completionHandler];
[connection start];
} else if (completionHandler) {
completionHandler(nil, nil, [FBSDKError
errorWithCode:FBSDKErrorAccessTokenRequired
message:@"No current access token to refresh"]);
}
}
#pragma mark - Equality
- (NSUInteger)hash
{
NSUInteger subhashes[] = {
self.tokenString.hash,
self.permissions.hash,
self.declinedPermissions.hash,
self.expiredPermissions.hash,
self.appID.hash,
self.userID.hash,
self.refreshDate.hash,
self.expirationDate.hash,
self.dataAccessExpirationDate.hash,
self.graphDomain.hash
};
return [FBSDKMath hashWithIntegerArray:subhashes count:sizeof(subhashes) / sizeof(subhashes[0])];
}
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
if (![object isKindOfClass:[FBSDKAccessToken class]]) {
return NO;
}
return [self isEqualToAccessToken:(FBSDKAccessToken *)object];
}
- (BOOL)isEqualToAccessToken:(FBSDKAccessToken *)token
{
return (token &&
[FBSDKInternalUtility object:self.tokenString isEqualToObject:token.tokenString] &&
[FBSDKInternalUtility object:self.permissions isEqualToObject:token.permissions] &&
[FBSDKInternalUtility object:self.declinedPermissions isEqualToObject:token.declinedPermissions] &&
[FBSDKInternalUtility object:self.expiredPermissions isEqualToObject:token.expiredPermissions] &&
[FBSDKInternalUtility object:self.appID isEqualToObject:token.appID] &&
[FBSDKInternalUtility object:self.userID isEqualToObject:token.userID] &&
[FBSDKInternalUtility object:self.refreshDate isEqualToObject:token.refreshDate] &&
[FBSDKInternalUtility object:self.expirationDate isEqualToObject:token.expirationDate] &&
[FBSDKInternalUtility object:self.dataAccessExpirationDate isEqualToObject:token.dataAccessExpirationDate] &&
[FBSDKInternalUtility object:self.graphDomain isEqualToObject:token.graphDomain]);
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
// we're immutable.
return self;
}
#pragma mark NSCoding
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
NSString *appID = [decoder decodeObjectOfClass:[NSString class] forKey:FBSDK_ACCESSTOKEN_APPID_KEY];
NSSet *declinedPermissions = [decoder decodeObjectOfClass:[NSSet class] forKey:FBSDK_ACCESSTOKEN_DECLINEDPERMISSIONS_KEY];
NSSet *expiredPermissions = [decoder decodeObjectOfClass:[NSSet class] forKey:FBSDK_ACCESSTOKEN_EXPIREDPERMISSIONS_KEY];
NSSet *permissions = [decoder decodeObjectOfClass:[NSSet class] forKey:FBSDK_ACCESSTOKEN_PERMISSIONS_KEY];
NSString *tokenString = [decoder decodeObjectOfClass:[NSString class] forKey:FBSDK_ACCESSTOKEN_TOKENSTRING_KEY];
NSString *userID = [decoder decodeObjectOfClass:[NSString class] forKey:FBSDK_ACCESSTOKEN_USERID_KEY];
NSDate *refreshDate = [decoder decodeObjectOfClass:[NSDate class] forKey:FBSDK_ACCESSTOKEN_REFRESHDATE_KEY];
NSDate *expirationDate = [decoder decodeObjectOfClass:[NSDate class] forKey:FBSDK_ACCESSTOKEN_EXPIRATIONDATE_KEY];
NSDate *dataAccessExpirationDate = [decoder decodeObjectOfClass:[NSDate class] forKey:FBSDK_ACCESSTOKEN_DATA_EXPIRATIONDATE_KEY];
NSString *graphDomain = [decoder decodeObjectOfClass:[NSString class] forKey:FBSDK_ACCESSTOKEN_GRAPH_DOMAIN_KEY];
return
[self
initWithTokenString:tokenString
permissions:permissions.allObjects
declinedPermissions:declinedPermissions.allObjects
expiredPermissions:expiredPermissions.allObjects
appID:appID
userID:userID
expirationDate:expirationDate
refreshDate:refreshDate
dataAccessExpirationDate:dataAccessExpirationDate
graphDomain:graphDomain];
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.appID forKey:FBSDK_ACCESSTOKEN_APPID_KEY];
[encoder encodeObject:self.declinedPermissions forKey:FBSDK_ACCESSTOKEN_DECLINEDPERMISSIONS_KEY];
[encoder encodeObject:self.expiredPermissions forKey:FBSDK_ACCESSTOKEN_EXPIREDPERMISSIONS_KEY];
[encoder encodeObject:self.permissions forKey:FBSDK_ACCESSTOKEN_PERMISSIONS_KEY];
[encoder encodeObject:self.tokenString forKey:FBSDK_ACCESSTOKEN_TOKENSTRING_KEY];
[encoder encodeObject:self.userID forKey:FBSDK_ACCESSTOKEN_USERID_KEY];
[encoder encodeObject:self.expirationDate forKey:FBSDK_ACCESSTOKEN_EXPIRATIONDATE_KEY];
[encoder encodeObject:self.refreshDate forKey:FBSDK_ACCESSTOKEN_REFRESHDATE_KEY];
[encoder encodeObject:self.dataAccessExpirationDate forKey:FBSDK_ACCESSTOKEN_DATA_EXPIRATIONDATE_KEY];
[encoder encodeObject:self.graphDomain forKey:FBSDK_ACCESSTOKEN_GRAPH_DOMAIN_KEY];
}
@end

View File

@ -0,0 +1,112 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
The FBSDKApplicationDelegate is designed to post process the results from Facebook Login
or Facebook Dialogs (or any action that requires switching over to the native Facebook
app or Safari).
The methods in this class are designed to mirror those in UIApplicationDelegate, and you
should call them in the respective methods in your AppDelegate implementation.
*/
NS_SWIFT_NAME(ApplicationDelegate)
@interface FBSDKApplicationDelegate : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
Gets the singleton instance.
*/
@property (class, nonatomic, readonly, strong) FBSDKApplicationDelegate *sharedInstance
NS_SWIFT_NAME(shared);
/**
Call this method from the [UIApplicationDelegate application:openURL:sourceApplication:annotation:] method
of the AppDelegate for your app. It should be invoked for the proper processing of responses during interaction
with the native Facebook app or Safari as part of SSO authorization flow or Facebook dialogs.
@param application The application as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:].
@param url The URL as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:].
@param sourceApplication The sourceApplication as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:].
@param annotation The annotation as passed to [UIApplicationDelegate application:openURL:sourceApplication:annotation:].
@return YES if the url was intended for the Facebook SDK, NO if not.
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(nullable NSString *)sourceApplication
annotation:(nullable id)annotation;
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_9_0
/**
Call this method from the [UIApplicationDelegate application:openURL:options:] method
of the AppDelegate for your app. It should be invoked for the proper processing of responses during interaction
with the native Facebook app or Safari as part of SSO authorization flow or Facebook dialogs.
@param application The application as passed to [UIApplicationDelegate application:openURL:options:].
@param url The URL as passed to [UIApplicationDelegate application:openURL:options:].
@param options The options dictionary as passed to [UIApplicationDelegate application:openURL:options:].
@return YES if the url was intended for the Facebook SDK, NO if not.
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;
#endif
/**
Call this method from the [UIApplicationDelegate application:didFinishLaunchingWithOptions:] method
of the AppDelegate for your app. It should be invoked for the proper use of the Facebook SDK.
As part of SDK initialization basic auto logging of app events will occur, this can be
controlled via 'FacebookAutoLogAppEventsEnabled' key in the project info plist file.
@param application The application as passed to [UIApplicationDelegate application:didFinishLaunchingWithOptions:].
@param launchOptions The launchOptions as passed to [UIApplicationDelegate application:didFinishLaunchingWithOptions:].
@return YES if the url was intended for the Facebook SDK, NO if not.
*/
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions;
/**
Call this method to manually initialize SDK.
As we initialize SDK automatically, this should only be called when auto initialization is disabled, this can be
controlled via 'FacebookAutoInitEnabled' key in the project info plist file.
@param launchOptions The launchOptions as passed to [UIApplicationDelegate application:didFinishLaunchingWithOptions:].
Could be nil if you don't call this function from [UIApplicationDelegate application:didFinishLaunchingWithOptions:].
*/
+ (void)initializeSDK:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,428 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKApplicationDelegate.h"
#import "FBSDKApplicationDelegate+Internal.h"
#import <objc/runtime.h>
#import "FBSDKAppEvents+Internal.h"
#import "FBSDKConstants.h"
#import "FBSDKDynamicFrameworkLoader.h"
#import "FBSDKError.h"
#import "FBSDKEventDeactivationManager.h"
#import "FBSDKFeatureManager.h"
#import "FBSDKGateKeeperManager.h"
#import "FBSDKInstrumentManager.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKLogger.h"
#import "FBSDKServerConfiguration.h"
#import "FBSDKServerConfigurationManager.h"
#import "FBSDKSettings+Internal.h"
#import "FBSDKTimeSpentData.h"
#if !TARGET_OS_TV
#import "FBSDKMeasurementEventListener.h"
#import "FBSDKContainerViewController.h"
#import "FBSDKProfile+Internal.h"
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
NSNotificationName const FBSDKApplicationDidBecomeActiveNotification = @"com.facebook.sdk.FBSDKApplicationDidBecomeActiveNotification";
#else
NSString *const FBSDKApplicationDidBecomeActiveNotification = @"com.facebook.sdk.FBSDKApplicationDidBecomeActiveNotification";
#endif
static NSString *const FBSDKAppLinkInboundEvent = @"fb_al_inbound";
static NSString *const FBSDKKitsBitmaskKey = @"com.facebook.sdk.kits.bitmask";
static BOOL g_isSDKInitialized = NO;
static UIApplicationState _applicationState;
@implementation FBSDKApplicationDelegate
{
NSHashTable<id<FBSDKApplicationObserving>> *_applicationObservers;
BOOL _isAppLaunched;
}
#pragma mark - Class Methods
+ (void)load
{
if ([FBSDKSettings isAutoInitEnabled]) {
// when the app becomes active by any means, kick off the initialization.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(initializeWithLaunchData:)
name:UIApplicationDidFinishLaunchingNotification
object:nil];
}
}
// Initialize SDK listeners
// Don't call this function in any place else. It should only be called when the class is loaded.
+ (void)initializeWithLaunchData:(NSNotification *)note
{
[self initializeSDK:note.userInfo];
// Remove the observer
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidFinishLaunchingNotification
object:nil];
}
+ (void)initializeSDK:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions
{
if (g_isSDKInitialized) {
// Do nothing if initialized already
return;
}
g_isSDKInitialized = YES;
FBSDKApplicationDelegate *delegate = [self sharedInstance];
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:delegate selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[defaultCenter addObserver:delegate selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[FBSDKAppEvents singleton] registerNotifications];
[delegate application:[UIApplication sharedApplication] didFinishLaunchingWithOptions:launchOptions];
[FBSDKFeatureManager checkFeature:FBSDKFeatureInstrument completionBlock:^(BOOL enabled) {
if (enabled) {
[FBSDKInstrumentManager enable];
}
}];
[FBSDKFeatureManager checkFeature:FBSDKFeatureRestrictiveDataFiltering completionBlock:^(BOOL enabled) {
if (enabled) {
[FBSDKRestrictiveDataFilterManager enable];
}
}];
[FBSDKFeatureManager checkFeature:FBSDKFeatureEventDeactivation completionBlock:^(BOOL enabled) {
if (enabled) {
[FBSDKEventDeactivationManager enable];
}
}];
#if !TARGET_OS_TV
// Register Listener for App Link measurement events
[FBSDKMeasurementEventListener defaultListener];
[delegate _logIfAutoAppLinkEnabled];
#endif
// Set the SourceApplication for time spent data. This is not going to update the value if the app has already launched.
[FBSDKTimeSpentData setSourceApplication:launchOptions[UIApplicationLaunchOptionsSourceApplicationKey]
openURL:launchOptions[UIApplicationLaunchOptionsURLKey]];
// Register on UIApplicationDidEnterBackgroundNotification events to reset source application data when app backgrounds.
[FBSDKTimeSpentData registerAutoResetSourceApplication];
[FBSDKInternalUtility validateFacebookReservedURLSchemes];
}
+ (FBSDKApplicationDelegate *)sharedInstance
{
static FBSDKApplicationDelegate *_sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
#pragma mark - Object Lifecycle
- (instancetype)init
{
if ((self = [super init]) != nil) {
_applicationObservers = [[NSHashTable alloc] init];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - UIApplicationDelegate
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_9_0
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
if (@available(iOS 9.0, *)) {
return [self application:application
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
}
return NO;
}
#endif
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
if (sourceApplication != nil && ![sourceApplication isKindOfClass:[NSString class]]) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"Expected 'sourceApplication' to be NSString. Please verify you are passing in 'sourceApplication' from your app delegate (not the UIApplication* parameter). If your app delegate implements iOS 9's application:openURL:options:, you should pass in options[UIApplicationOpenURLOptionsSourceApplicationKey]. "
userInfo:nil];
}
[FBSDKTimeSpentData setSourceApplication:sourceApplication openURL:url];
BOOL handled = NO;
NSArray<id<FBSDKApplicationObserving>> *observers = [_applicationObservers allObjects];
for (id<FBSDKApplicationObserving> observer in observers) {
if ([observer respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]) {
if ([observer application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation]) {
handled = YES;
}
}
}
if (handled) {
return YES;
}
[self _logIfAppLinkEvent:url];
return NO;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([self isAppLaunched]) {
return NO;
}
_isAppLaunched = YES;
FBSDKAccessToken *cachedToken = [FBSDKSettings accessTokenCache].accessToken;
[FBSDKAccessToken setCurrentAccessToken:cachedToken];
// fetch app settings
[FBSDKServerConfigurationManager loadServerConfigurationWithCompletionBlock:NULL];
if (FBSDKSettings.isAutoLogAppEventsEnabled) {
[self _logSDKInitialize];
}
#if !TARGET_OS_TV
FBSDKProfile *cachedProfile = [FBSDKProfile fetchCachedProfile];
[FBSDKProfile setCurrentProfile:cachedProfile];
#endif
NSArray<id<FBSDKApplicationObserving>> *observers = [_applicationObservers allObjects];
BOOL handled = NO;
for (id<FBSDKApplicationObserving> observer in observers) {
if ([observer respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) {
if ([observer application:application didFinishLaunchingWithOptions:launchOptions]) {
handled = YES;
}
}
}
return handled;
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
_applicationState = UIApplicationStateBackground;
NSArray<id<FBSDKApplicationObserving>> *observers = [_applicationObservers allObjects];
for (id<FBSDKApplicationObserving> observer in observers) {
if ([observer respondsToSelector:@selector(applicationDidEnterBackground:)]) {
[observer applicationDidEnterBackground:notification.object];
}
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
_applicationState = UIApplicationStateActive;
// Auto log basic events in case autoLogAppEventsEnabled is set
if (FBSDKSettings.isAutoLogAppEventsEnabled) {
[FBSDKAppEvents activateApp];
}
NSArray<id<FBSDKApplicationObserving>> *observers = [_applicationObservers copy];
for (id<FBSDKApplicationObserving> observer in observers) {
if ([observer respondsToSelector:@selector(applicationDidBecomeActive:)]) {
[observer applicationDidBecomeActive:notification.object];
}
}
}
#pragma mark - Internal Methods
#pragma mark - FBSDKApplicationObserving
- (void)addObserver:(id<FBSDKApplicationObserving>)observer
{
if (![_applicationObservers containsObject:observer]) {
[_applicationObservers addObject:observer];
}
}
- (void)removeObserver:(id<FBSDKApplicationObserving>)observer
{
if ([_applicationObservers containsObject:observer]) {
[_applicationObservers removeObject:observer];
}
}
+ (UIApplicationState)applicationState
{
return _applicationState;
}
#pragma mark - Helper Methods
- (void)_logIfAppLinkEvent:(NSURL *)url
{
if (!url) {
return;
}
NSDictionary<NSString *, NSString *> *params = [FBSDKBasicUtility dictionaryWithQueryString:url.query];
NSString *applinkDataString = params[@"al_applink_data"];
if (!applinkDataString) {
return;
}
NSDictionary<id, id> *applinkData = [FBSDKBasicUtility objectForJSONString:applinkDataString error:NULL];
if (!applinkData) {
return;
}
NSString *targetURLString = applinkData[@"target_url"];
NSURL *targetURL = [targetURLString isKindOfClass:[NSString class]] ? [NSURL URLWithString:targetURLString] : nil;
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
[FBSDKBasicUtility dictionary:logData setObject:targetURL.absoluteString forKey:@"targetURL"];
[FBSDKBasicUtility dictionary:logData setObject:targetURL.host forKey:@"targetURLHost"];
NSDictionary *refererData = applinkData[@"referer_data"];
if (refererData) {
[FBSDKBasicUtility dictionary:logData setObject:refererData[@"target_url"] forKey:@"referralTargetURL"];
[FBSDKBasicUtility dictionary:logData setObject:refererData[@"url"] forKey:@"referralURL"];
[FBSDKBasicUtility dictionary:logData setObject:refererData[@"app_name"] forKey:@"referralAppName"];
}
[FBSDKBasicUtility dictionary:logData setObject:url.absoluteString forKey:@"inputURL"];
[FBSDKBasicUtility dictionary:logData setObject:url.scheme forKey:@"inputURLScheme"];
[FBSDKAppEvents logInternalEvent:FBSDKAppLinkInboundEvent
parameters:logData
isImplicitlyLogged:YES];
}
- (void)_logSDKInitialize
{
NSDictionary *metaInfo = [NSDictionary dictionaryWithObjects:@[@"login_lib_included",
@"marketing_lib_included",
@"messenger_lib_included",
@"places_lib_included",
@"share_lib_included",
@"tv_lib_included"]
forKeys:@[@"FBSDKLoginManager",
@"FBSDKAutoLog",
@"FBSDKMessengerButton",
@"FBSDKPlacesManager",
@"FBSDKShareDialog",
@"FBSDKTVInterfaceFactory"]];
NSInteger bitmask = 0;
NSInteger bit = 0;
NSMutableDictionary<NSString *, NSNumber *> *params = NSMutableDictionary.new;
params[@"core_lib_included"] = @1;
for (NSString *className in metaInfo.allKeys) {
NSString *keyName = [metaInfo objectForKey:className];
if (objc_lookUpClass([className UTF8String])) {
params[keyName] = @1;
bitmask |= 1 << bit;
}
bit++;
}
// Tracking if the consuming Application is using Swift
id delegate = [UIApplication sharedApplication].delegate;
NSString const *className = NSStringFromClass([delegate class]);
if ([className componentsSeparatedByString:@"."].count > 1) {
params[@"is_using_swift"] = @YES;
}
void (^checkViewForSwift)(void) = ^void ()
{
// Additional check to see if the consuming application perhaps was
// originally an objc project but is now using Swift
UIViewController *topMostViewController = [FBSDKInternalUtility topMostViewController];
NSString const *vcClassName = NSStringFromClass([topMostViewController class]);
if ([vcClassName componentsSeparatedByString:@"."].count > 1) {
params[@"is_using_swift"] = @YES;
}
};
if ([NSThread isMainThread]) {
checkViewForSwift();
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
checkViewForSwift();
});
}
NSInteger existingBitmask = [[NSUserDefaults standardUserDefaults] integerForKey:FBSDKKitsBitmaskKey];
if (existingBitmask != bitmask) {
[[NSUserDefaults standardUserDefaults] setInteger:bitmask forKey:FBSDKKitsBitmaskKey];
[FBSDKAppEvents logInternalEvent:@"fb_sdk_initialize"
parameters:params
isImplicitlyLogged:NO];
}
}
- (void)_logIfAutoAppLinkEnabled
{
#if !TARGET_OS_TV
NSNumber *enabled = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FBSDKAutoAppLinkEnabled"];
if (enabled.boolValue) {
NSMutableDictionary<NSString *, NSString *> *params = [[NSMutableDictionary alloc] init];
if (![FBSDKAppLinkUtility isMatchURLScheme:[NSString stringWithFormat:@"fb%@", [FBSDKSettings appID]]]) {
NSString *warning = @"You haven't set the Auto App Link URL scheme: fb<YOUR APP ID>";
params[@"SchemeWarning"] = warning;
NSLog(@"%@", warning);
}
[FBSDKAppEvents logInternalEvent:@"fb_auto_applink" parameters:params isImplicitlyLogged:YES];
}
#endif
}
+ (BOOL)isSDKInitialized
{
return [FBSDKSettings isAutoInitEnabled] || g_isSDKInitialized;
}
// Wrapping this makes it mockable and enables testability
- (BOOL)isAppLaunched {
return _isAppLaunched;
}
@end

View File

@ -0,0 +1,31 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
A base class for common SDK buttons.
*/
NS_SWIFT_NAME(FBButton)
@interface FBSDKButton : UIButton
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,465 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKButton.h"
#import "FBSDKButton+Subclass.h"
#import "FBSDKAccessToken.h"
#import "FBSDKAppEvents+Internal.h"
#import "FBSDKAppEvents.h"
#import "FBSDKApplicationDelegate+Internal.h"
#import "FBSDKLogo.h"
#import "FBSDKUIUtility.h"
#import "FBSDKViewImpressionTracker.h"
#define HEIGHT_TO_FONT_SIZE 0.47
#define HEIGHT_TO_MARGIN 0.27
#define HEIGHT_TO_PADDING 0.23
#define HEIGHT_TO_TEXT_PADDING_CORRECTION 0.08
@implementation FBSDKButton
{
BOOL _skipIntrinsicContentSizing;
BOOL _isExplicitlyDisabled;
}
#pragma mark - Object Lifecycle
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_skipIntrinsicContentSizing = YES;
[self configureButton];
_skipIntrinsicContentSizing = NO;
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
_skipIntrinsicContentSizing = YES;
[self configureButton];
_skipIntrinsicContentSizing = NO;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Properties
- (void)setEnabled:(BOOL)enabled
{
_isExplicitlyDisabled = !enabled;
[self checkImplicitlyDisabled];
}
#pragma mark - Layout
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
if (self.hidden || CGRectIsEmpty(self.bounds)) {
return CGRectZero;
}
CGRect imageRect = UIEdgeInsetsInsetRect(contentRect, self.imageEdgeInsets);
CGFloat margin = [self _marginForHeight:[self _heightForContentRect:contentRect]];
imageRect = CGRectInset(imageRect, margin, margin);
imageRect.size.width = CGRectGetHeight(imageRect);
return imageRect;
}
- (CGSize)intrinsicContentSize
{
if (_skipIntrinsicContentSizing) {
return CGSizeZero;
}
_skipIntrinsicContentSizing = YES;
CGSize size = [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
_skipIntrinsicContentSizing = NO;
return size;
}
- (void)layoutSubviews
{
// automatic impression tracking if the button conforms to FBSDKButtonImpressionTracking
if ([self conformsToProtocol:@protocol(FBSDKButtonImpressionTracking)]) {
NSString *eventName = ((id<FBSDKButtonImpressionTracking>)self).impressionTrackingEventName;
NSString *identifier = ((id<FBSDKButtonImpressionTracking>)self).impressionTrackingIdentifier;
NSDictionary<NSString *, id> *parameters = ((id<FBSDKButtonImpressionTracking>)self).analyticsParameters;
if (eventName && identifier) {
FBSDKViewImpressionTracker *impressionTracker = [FBSDKViewImpressionTracker impressionTrackerWithEventName:eventName];
[impressionTracker logImpressionWithIdentifier:identifier parameters:parameters];
}
}
[super layoutSubviews];
}
- (CGSize)sizeThatFits:(CGSize)size
{
if (self.hidden) {
return CGSizeZero;
}
CGSize normalSize = [self sizeThatFits:size title:[self titleForState:UIControlStateNormal]];
CGSize selectedSize = [self sizeThatFits:size title:[self titleForState:UIControlStateSelected]];
return CGSizeMake(MAX(normalSize.width, selectedSize.width), MAX(normalSize.height, selectedSize.height));
}
- (void)sizeToFit
{
CGRect bounds = self.bounds;
bounds.size = [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
self.bounds = bounds;
}
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
if (self.hidden || CGRectIsEmpty(self.bounds)) {
return CGRectZero;
}
CGRect imageRect = [self imageRectForContentRect:contentRect];
CGFloat height = [self _heightForContentRect:contentRect];
CGFloat padding = [self _paddingForHeight:height];
CGFloat titleX = CGRectGetMaxX(imageRect) + padding;
CGRect titleRect = CGRectMake(titleX, 0.0, CGRectGetWidth(contentRect) - titleX, CGRectGetHeight(contentRect));
UIEdgeInsets titleEdgeInsets = UIEdgeInsetsZero;
if (!self.layer.needsLayout) {
UILabel *titleLabel = self.titleLabel;
if (titleLabel.textAlignment == NSTextAlignmentCenter) {
// if the text is centered, we need to adjust the frame for the titleLabel based on the size of the text in order
// to keep the text centered in the button without adding extra blank space to the right when unnecessary
// 1. the text fits centered within the button without colliding with the image (imagePaddingWidth)
// 2. the text would run into the image, so adjust the insets to effectively left align it (textPaddingWidth)
CGSize titleSize = FBSDKTextSize(titleLabel.text,
titleLabel.font,
titleRect.size,
titleLabel.lineBreakMode);
CGFloat titlePaddingWidth = (CGRectGetWidth(titleRect) - titleSize.width) / 2;
CGFloat imagePaddingWidth = titleX / 2;
CGFloat inset = MIN(titlePaddingWidth, imagePaddingWidth);
titleEdgeInsets.left -= inset;
titleEdgeInsets.right += inset;
}
}
return UIEdgeInsetsInsetRect(titleRect, titleEdgeInsets);
}
#pragma mark - Subclass Methods
- (void)logTapEventWithEventName:(NSString *)eventName parameters:(NSDictionary *)parameters
{
[FBSDKAppEvents logInternalEvent:eventName
parameters:parameters
isImplicitlyLogged:YES
accessToken:[FBSDKAccessToken currentAccessToken]];
}
- (void)checkImplicitlyDisabled
{
BOOL enabled = !_isExplicitlyDisabled && !self.implicitlyDisabled;
BOOL currentEnabled = self.enabled;
super.enabled = enabled;
if (currentEnabled != enabled) {
[self invalidateIntrinsicContentSize];
[self setNeedsLayout];
}
}
- (void)configureButton
{
[self configureWithIcon:[self defaultIcon]
title:nil
backgroundColor:[self defaultBackgroundColor]
highlightedColor:[self defaultHighlightedColor]];
}
- (void)configureWithIcon:(FBSDKIcon *)icon
title:(NSString *)title
backgroundColor:(UIColor *)backgroundColor
highlightedColor:(UIColor *)highlightedColor
{
[self _configureWithIcon:icon
title:title
backgroundColor:backgroundColor
highlightedColor:highlightedColor
selectedTitle:nil
selectedIcon:nil
selectedColor:nil
selectedHighlightedColor:nil];
}
- (void)configureWithIcon:(FBSDKIcon *)icon
title:(NSString *)title
backgroundColor:(UIColor *)backgroundColor
highlightedColor:(UIColor *)highlightedColor
selectedTitle:(NSString *)selectedTitle
selectedIcon:(FBSDKIcon *)selectedIcon
selectedColor:(UIColor *)selectedColor
selectedHighlightedColor:(UIColor *)selectedHighlightedColor
{
[self _configureWithIcon:icon
title:title
backgroundColor:backgroundColor
highlightedColor:highlightedColor
selectedTitle:selectedTitle
selectedIcon:selectedIcon
selectedColor:selectedColor
selectedHighlightedColor:selectedHighlightedColor];
}
- (UIColor *)defaultBackgroundColor
{
return [UIColor colorWithRed:24.0/255.0 green:119.0/255.0 blue:242.0/255.0 alpha:1.0];
}
- (UIColor *)defaultDisabledColor
{
return [UIColor colorWithRed:189.0/255.0 green:193.0/255.0 blue:201.0/255.0 alpha:1.0];
}
- (UIFont *)defaultFont
{
return [UIFont systemFontOfSize:14];
}
- (UIColor *)defaultHighlightedColor
{
return [UIColor colorWithRed:21.0/255.0 green:105.0/255.0 blue:214.0/255.0 alpha:1.0];
}
- (FBSDKIcon *)defaultIcon
{
return [[FBSDKLogo alloc] init];
}
- (UIColor *)defaultSelectedColor
{
return [self defaultBackgroundColor];
}
- (UIColor *)highlightedContentColor
{
return [UIColor colorWithRed:218.0/255.0 green:221.0/255.0 blue:226.0/255.0 alpha:1.0];
}
- (BOOL)isImplicitlyDisabled
{
return NO;
}
- (CGSize)sizeThatFits:(CGSize)size title:(NSString *)title
{
UIFont *font = self.titleLabel.font;
CGFloat height = [self _heightForFont:font];
UIEdgeInsets contentEdgeInsets = self.contentEdgeInsets;
CGSize constrainedContentSize = FBSDKEdgeInsetsInsetSize(size, contentEdgeInsets);
CGSize titleSize = FBSDKTextSize(title, font, constrainedContentSize, self.titleLabel.lineBreakMode);
CGFloat padding = [self _paddingForHeight:height];
CGFloat textPaddingCorrection = [self _textPaddingCorrectionForHeight:height];
CGSize contentSize = CGSizeMake(height + padding + titleSize.width - textPaddingCorrection, height);
return FBSDKEdgeInsetsOutsetSize(contentSize, contentEdgeInsets);
}
#pragma mark - Helper Methods
- (void)_applicationDidBecomeActiveNotification:(NSNotification *)notification
{
[self checkImplicitlyDisabled];
}
- (UIImage *)_backgroundImageWithColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius scale:(CGFloat)scale
{
CGFloat size = 1.0 + 2 * cornerRadius;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size, size), NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, cornerRadius + 1.0, 0.0);
CGPathAddArcToPoint(path, NULL, size, 0.0, size, cornerRadius, cornerRadius);
CGPathAddLineToPoint(path, NULL, size, cornerRadius + 1.0);
CGPathAddArcToPoint(path, NULL, size, size, cornerRadius + 1.0, size, cornerRadius);
CGPathAddLineToPoint(path, NULL, cornerRadius, size);
CGPathAddArcToPoint(path, NULL, 0.0, size, 0.0, cornerRadius + 1.0, cornerRadius);
CGPathAddLineToPoint(path, NULL, 0.0, cornerRadius);
CGPathAddArcToPoint(path, NULL, 0.0, 0.0, cornerRadius, 0.0, cornerRadius);
CGPathCloseSubpath(path);
CGContextAddPath(context, path);
CGPathRelease(path);
CGContextFillPath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
#if TARGET_OS_TV
return [image resizableImageWithCapInsets:UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius)
resizingMode:UIImageResizingModeStretch];
#else
return [image stretchableImageWithLeftCapWidth:cornerRadius topCapHeight:cornerRadius];
#endif
}
- (void)_configureWithIcon:(FBSDKIcon *)icon
title:(NSString *)title
backgroundColor:(UIColor *)backgroundColor
highlightedColor:(UIColor *)highlightedColor
selectedTitle:(NSString *)selectedTitle
selectedIcon:(FBSDKIcon *)selectedIcon
selectedColor:(UIColor *)selectedColor
selectedHighlightedColor:(UIColor *)selectedHighlightedColor
{
[self checkImplicitlyDisabled];
if (!icon) {
icon = [self defaultIcon];
}
if (!selectedIcon) {
selectedIcon = [self defaultIcon];
}
if (!backgroundColor) {
backgroundColor = [self defaultBackgroundColor];
}
if (!highlightedColor) {
highlightedColor = [self defaultHighlightedColor];
}
if (!selectedColor) {
selectedColor = [self defaultSelectedColor];
}
if (!selectedHighlightedColor) {
selectedHighlightedColor = highlightedColor;
}
self.adjustsImageWhenDisabled = NO;
self.adjustsImageWhenHighlighted = NO;
self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
self.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
self.tintColor = [UIColor whiteColor];
BOOL forceSizeToFit = CGRectIsEmpty(self.bounds);
CGFloat scale = [UIScreen mainScreen].scale;
UIImage *backgroundImage;
backgroundImage = [self _backgroundImageWithColor:backgroundColor cornerRadius:3.0 scale:scale];
[self setBackgroundImage:backgroundImage forState:UIControlStateNormal];
#if TARGET_OS_TV
[self setBackgroundImage:backgroundImage forState:UIControlStateFocused];
#endif
backgroundImage = [self _backgroundImageWithColor:highlightedColor cornerRadius:3.0 scale:scale];
[self setBackgroundImage:backgroundImage forState:UIControlStateHighlighted];
backgroundImage = [self _backgroundImageWithColor:[self defaultDisabledColor] cornerRadius:3.0 scale:scale];
[self setBackgroundImage:backgroundImage forState:UIControlStateDisabled];
if (selectedColor) {
backgroundImage = [self _backgroundImageWithColor:selectedColor cornerRadius:3.0 scale:scale];
[self setBackgroundImage:backgroundImage forState:UIControlStateSelected];
}
if (selectedHighlightedColor) {
backgroundImage = [self _backgroundImageWithColor:selectedHighlightedColor cornerRadius:3.0 scale:scale];
[self setBackgroundImage:backgroundImage forState:UIControlStateSelected | UIControlStateHighlighted];
#if TARGET_OS_TV
[self setBackgroundImage:backgroundImage forState:UIControlStateSelected | UIControlStateFocused];
#endif
}
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[self setTitleColor:[self highlightedContentColor] forState: UIControlStateHighlighted | UIControlStateSelected];
[self setTitle:title forState:UIControlStateNormal];
#if TARGET_OS_TV
[self setTitle:title forState:UIControlStateFocused];
#endif
if (selectedTitle) {
[self setTitle:selectedTitle forState:UIControlStateSelected];
[self setTitle:selectedTitle forState:UIControlStateSelected | UIControlStateHighlighted];
#if TARGET_OS_TV
[self setTitle:selectedTitle forState:UIControlStateSelected | UIControlStateFocused];
#endif
}
UILabel *titleLabel = self.titleLabel;
titleLabel.lineBreakMode = NSLineBreakByClipping;
UIFont *font = [self defaultFont];
titleLabel.font = font;
CGSize imageSize = CGSizeMake(font.pointSize, font.pointSize);
UIImage *image = [icon imageWithSize:imageSize];
image = [image resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeStretch];
[self setImage:image forState:UIControlStateNormal];
#if TARGET_OS_TV
[self setImage:image forState:UIControlStateFocused];
#endif
if (selectedIcon) {
UIImage *selectedImage = [selectedIcon imageWithSize:imageSize];
selectedImage = [selectedImage resizableImageWithCapInsets:UIEdgeInsetsZero
resizingMode:UIImageResizingModeStretch];
[self setImage:selectedImage forState:UIControlStateSelected];
[self setImage:selectedImage forState:UIControlStateSelected | UIControlStateHighlighted];
#if TARGET_OS_TV
[self setImage:selectedImage forState:UIControlStateSelected | UIControlStateFocused];
#endif
}
if (forceSizeToFit) {
[self sizeToFit];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_applicationDidBecomeActiveNotification:)
name:FBSDKApplicationDidBecomeActiveNotification
object:[FBSDKApplicationDelegate sharedInstance]];
}
- (CGFloat)_fontSizeForHeight:(CGFloat)height
{
return floorf(height * HEIGHT_TO_FONT_SIZE);
}
- (CGFloat)_heightForContentRect:(CGRect)contentRect
{
UIEdgeInsets contentEdgeInsets = self.contentEdgeInsets;
return contentEdgeInsets.top + CGRectGetHeight(contentRect) + contentEdgeInsets.bottom;
}
- (CGFloat)_heightForFont:(UIFont *)font
{
return floorf(font.pointSize / (1 - 2 * HEIGHT_TO_MARGIN));
}
- (CGFloat)_marginForHeight:(CGFloat)height
{
return floorf(height * HEIGHT_TO_MARGIN);
}
- (CGFloat)_paddingForHeight:(CGFloat)height
{
return roundf(height * HEIGHT_TO_PADDING) - [self _textPaddingCorrectionForHeight:height];
}
- (CGFloat)_textPaddingCorrectionForHeight:(CGFloat)height
{
return floorf(height * HEIGHT_TO_TEXT_PADDING_CORRECTION);
}
@end

View File

@ -0,0 +1,375 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
/**
The error domain for all errors from FBSDKCoreKit.
Error codes from the SDK in the range 0-99 are reserved for this domain.
*/
FOUNDATION_EXPORT NSErrorDomain const FBSDKErrorDomain
NS_SWIFT_NAME(ErrorDomain);
#else
/**
The error domain for all errors from FBSDKCoreKit.
Error codes from the SDK in the range 0-99 are reserved for this domain.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorDomain
NS_SWIFT_NAME(ErrorDomain);
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
/*
@methodgroup error userInfo keys
*/
/**
The userInfo key for the invalid collection for errors with FBSDKErrorInvalidArgument.
If the invalid argument is a collection, the collection can be found with this key and the individual
invalid item can be found with FBSDKErrorArgumentValueKey.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKErrorArgumentCollectionKey
NS_SWIFT_NAME(ErrorArgumentCollectionKey);
/**
The userInfo key for the invalid argument name for errors with FBSDKErrorInvalidArgument.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKErrorArgumentNameKey
NS_SWIFT_NAME(ErrorArgumentNameKey);
/**
The userInfo key for the invalid argument value for errors with FBSDKErrorInvalidArgument.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKErrorArgumentValueKey
NS_SWIFT_NAME(ErrorArgumentValueKey);
/**
The userInfo key for the message for developers in NSErrors that originate from the SDK.
The developer message will not be localized and is not intended to be presented within the app.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKErrorDeveloperMessageKey
NS_SWIFT_NAME(ErrorDeveloperMessageKey);
/**
The userInfo key describing a localized description that can be presented to the user.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKErrorLocalizedDescriptionKey
NS_SWIFT_NAME(ErrorLocalizedDescriptionKey);
/**
The userInfo key describing a localized title that can be presented to the user, used with `FBSDKLocalizedErrorDescriptionKey`.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKErrorLocalizedTitleKey
NS_SWIFT_NAME(ErrorLocalizedTitleKey);
/*
@methodgroup FBSDKGraphRequest error userInfo keys
*/
/**
The userInfo key describing the error category, for error recovery purposes.
See `FBSDKGraphErrorRecoveryProcessor` and `[FBSDKGraphRequest disableErrorRecovery]`.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKGraphRequestErrorKey
NS_SWIFT_NAME(GraphRequestErrorKey);
/*
The userInfo key for the Graph API error code.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKGraphRequestErrorGraphErrorCodeKey
NS_SWIFT_NAME(GraphRequestErrorGraphErrorCodeKey);
/*
The userInfo key for the Graph API error subcode.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKGraphRequestErrorGraphErrorSubcodeKey
NS_SWIFT_NAME(GraphRequestErrorGraphErrorSubcodeKey);
/*
The userInfo key for the HTTP status code.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKGraphRequestErrorHTTPStatusCodeKey
NS_SWIFT_NAME(GraphRequestErrorHTTPStatusCodeKey);
/*
The userInfo key for the raw JSON response.
*/
FOUNDATION_EXPORT NSErrorUserInfoKey const FBSDKGraphRequestErrorParsedJSONResponseKey
NS_SWIFT_NAME(GraphRequestErrorParsedJSONResponseKey);
#else
/*
@methodgroup error userInfo keys
*/
/**
The userInfo key for the invalid collection for errors with FBSDKErrorInvalidArgument.
If the invalid argument is a collection, the collection can be found with this key and the individual
invalid item can be found with FBSDKErrorArgumentValueKey.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorArgumentCollectionKey
NS_SWIFT_NAME(ErrorArgumentCollectionKey);
/**
The userInfo key for the invalid argument name for errors with FBSDKErrorInvalidArgument.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorArgumentNameKey
NS_SWIFT_NAME(ErrorArgumentNameKey);
/**
The userInfo key for the invalid argument value for errors with FBSDKErrorInvalidArgument.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorArgumentValueKey
NS_SWIFT_NAME(ErrorArgumentValueKey);
/**
The userInfo key for the message for developers in NSErrors that originate from the SDK.
The developer message will not be localized and is not intended to be presented within the app.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorDeveloperMessageKey
NS_SWIFT_NAME(ErrorDeveloperMessageKey);
/**
The userInfo key describing a localized description that can be presented to the user.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorLocalizedDescriptionKey
NS_SWIFT_NAME(ErrorLocalizedDescriptionKey);
/**
The userInfo key describing a localized title that can be presented to the user, used with `FBSDKLocalizedErrorDescriptionKey`.
*/
FOUNDATION_EXPORT NSString *const FBSDKErrorLocalizedTitleKey
NS_SWIFT_NAME(ErrorLocalizedTitleKey);
/*
@methodgroup FBSDKGraphRequest error userInfo keys
*/
/**
The userInfo key describing the error category, for error recovery purposes.
See `FBSDKGraphErrorRecoveryProcessor` and `[FBSDKGraphRequest disableErrorRecovery]`.
*/
FOUNDATION_EXPORT NSString *const FBSDKGraphRequestErrorKey
NS_SWIFT_NAME(GraphRequestErrorKey);
/*
The userInfo key for the Graph API error code.
*/
FOUNDATION_EXPORT NSString *const FBSDKGraphRequestErrorGraphErrorCodeKey
NS_SWIFT_NAME(GraphRequestErrorGraphErrorCodeKey);
/*
The userInfo key for the Graph API error subcode.
*/
FOUNDATION_EXPORT NSString *const FBSDKGraphRequestErrorGraphErrorSubcodeKey
NS_SWIFT_NAME(GraphRequestErrorGraphErrorSubcodeKey);
/*
The userInfo key for the HTTP status code.
*/
FOUNDATION_EXPORT NSString *const FBSDKGraphRequestErrorHTTPStatusCodeKey
NS_SWIFT_NAME(GraphRequestErrorHTTPStatusCodeKey);
/*
The userInfo key for the raw JSON response.
*/
FOUNDATION_EXPORT NSString *const FBSDKGraphRequestErrorParsedJSONResponseKey
NS_SWIFT_NAME(GraphRequestErrorParsedJSONResponseKey);
#endif
/*
@methodgroup Common Code Block typedefs
*/
/**
Success Block
*/
typedef void (^FBSDKCodeBlock)(void)
NS_SWIFT_NAME(CodeBlock);
/**
Error Block
*/
typedef void (^FBSDKErrorBlock)(NSError *_Nullable error)
NS_SWIFT_NAME(ErrorBlock);
/**
Success Block
*/
typedef void (^FBSDKSuccessBlock)(BOOL success, NSError *_Nullable error)
NS_SWIFT_NAME(SuccessBlock);
/*
@methodgroup Enums
*/
#ifndef NS_ERROR_ENUM
#define NS_ERROR_ENUM(_domain, _name) \
enum _name: NSInteger _name; \
enum __attribute__((ns_error_domain(_domain))) _name: NSInteger
#endif
/**
FBSDKCoreError
Error codes for FBSDKErrorDomain.
*/
typedef NS_ERROR_ENUM(FBSDKErrorDomain, FBSDKCoreError)
{
/**
Reserved.
*/
FBSDKErrorReserved = 0,
/**
The error code for errors from invalid encryption on incoming encryption URLs.
*/
FBSDKErrorEncryption,
/**
The error code for errors from invalid arguments to SDK methods.
*/
FBSDKErrorInvalidArgument,
/**
The error code for unknown errors.
*/
FBSDKErrorUnknown,
/**
A request failed due to a network error. Use NSUnderlyingErrorKey to retrieve
the error object from the NSURLSession for more information.
*/
FBSDKErrorNetwork,
/**
The error code for errors encountered during an App Events flush.
*/
FBSDKErrorAppEventsFlush,
/**
An endpoint that returns a binary response was used with FBSDKGraphRequestConnection.
Endpoints that return image/jpg, etc. should be accessed using NSURLRequest
*/
FBSDKErrorGraphRequestNonTextMimeTypeReturned,
/**
The operation failed because the server returned an unexpected response.
You can get this error if you are not using the most recent SDK, or you are accessing a version of the
Graph API incompatible with the current SDK.
*/
FBSDKErrorGraphRequestProtocolMismatch,
/**
The Graph API returned an error.
See below for useful userInfo keys (beginning with FBSDKGraphRequestError*)
*/
FBSDKErrorGraphRequestGraphAPI,
/**
The specified dialog configuration is not available.
This error may signify that the configuration for the dialogs has not yet been downloaded from the server
or that the dialog is unavailable. Subsequent attempts to use the dialog may succeed as the configuration is loaded.
*/
FBSDKErrorDialogUnavailable,
/**
Indicates an operation failed because a required access token was not found.
*/
FBSDKErrorAccessTokenRequired,
/**
Indicates an app switch (typically for a dialog) failed because the destination app is out of date.
*/
FBSDKErrorAppVersionUnsupported,
/**
Indicates an app switch to the browser (typically for a dialog) failed.
*/
FBSDKErrorBrowserUnavailable,
/**
Indicates that a bridge api interaction was interrupted.
*/
FBSDKErrorBridgeAPIInterruption,
} NS_SWIFT_NAME(CoreError);
/**
FBSDKGraphRequestError
Describes the category of Facebook error. See `FBSDKGraphRequestErrorKey`.
*/
typedef NS_ENUM(NSUInteger, FBSDKGraphRequestError)
{
/** The default error category that is not known to be recoverable. Check `FBSDKLocalizedErrorDescriptionKey` for a user facing message. */
FBSDKGraphRequestErrorOther = 0,
/** Indicates the error is temporary (such as server throttling). While a recoveryAttempter will be provided with the error instance, the attempt is guaranteed to succeed so you can simply retry the operation if you do not want to present an alert. */
FBSDKGraphRequestErrorTransient = 1,
/** Indicates the error can be recovered (such as requiring a login). A recoveryAttempter will be provided with the error instance that can take UI action. */
FBSDKGraphRequestErrorRecoverable = 2
} NS_SWIFT_NAME(GraphRequestError);
/**
a formal protocol very similar to the informal protocol NSErrorRecoveryAttempting
*/
NS_SWIFT_UNAVAILABLE("")
@protocol FBSDKErrorRecoveryAttempting<NSObject>
/**
attempt the recovery
@param error the error
@param recoveryOptionIndex the selected option index
@param delegate the delegate
@param didRecoverSelector the callback selector, see discussion.
@param contextInfo context info to pass back to callback selector, see discussion.
Given that an error alert has been presented document-modally to the user, and the user has chosen one of the error's recovery options, attempt recovery from the error, and send the selected message to the specified delegate. The option index is an index into the error's array of localized recovery options. The method selected by didRecoverSelector must have the same signature as:
- (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)contextInfo;
The value passed for didRecover must be YES if error recovery was completely successful, NO otherwise.
*/
- (void)attemptRecoveryFromError:(NSError *)error
optionIndex:(NSUInteger)recoveryOptionIndex
delegate:(nullable id)delegate
didRecoverSelector:(SEL)didRecoverSelector
contextInfo:(nullable void *)contextInfo;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,64 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBSDKConstants.h"
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
NSErrorDomain const FBSDKErrorDomain = @"com.facebook.sdk.core";
#else
NSString *const FBSDKErrorDomain = @"com.facebook.sdk.core";
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
NSErrorUserInfoKey const FBSDKErrorArgumentCollectionKey = @"com.facebook.sdk:FBSDKErrorArgumentCollectionKey";
NSErrorUserInfoKey const FBSDKErrorArgumentNameKey = @"com.facebook.sdk:FBSDKErrorArgumentNameKey";
NSErrorUserInfoKey const FBSDKErrorArgumentValueKey = @"com.facebook.sdk:FBSDKErrorArgumentValueKey";
NSErrorUserInfoKey const FBSDKErrorDeveloperMessageKey = @"com.facebook.sdk:FBSDKErrorDeveloperMessageKey";
NSErrorUserInfoKey const FBSDKErrorLocalizedDescriptionKey = @"com.facebook.sdk:FBSDKErrorLocalizedDescriptionKey";
NSErrorUserInfoKey const FBSDKErrorLocalizedTitleKey = @"com.facebook.sdk:FBSDKErrorLocalizedErrorTitleKey";
NSErrorUserInfoKey const FBSDKGraphRequestErrorKey = @"com.facebook.sdk:FBSDKGraphRequestErrorKey";
NSErrorUserInfoKey const FBSDKGraphRequestErrorCategoryKey = @"com.facebook.sdk:FBSDKGraphRequestErrorCategoryKey";
NSErrorUserInfoKey const FBSDKGraphRequestErrorGraphErrorCodeKey = @"com.facebook.sdk:FBSDKGraphRequestErrorGraphErrorCodeKey";
NSErrorUserInfoKey const FBSDKGraphRequestErrorGraphErrorSubcodeKey = @"com.facebook.sdk:FBSDKGraphRequestErrorGraphErrorSubcodeKey";
NSErrorUserInfoKey const FBSDKGraphRequestErrorHTTPStatusCodeKey = @"com.facebook.sdk:FBSDKGraphRequestErrorHTTPStatusCodeKey";
NSErrorUserInfoKey const FBSDKGraphRequestErrorParsedJSONResponseKey = @"com.facebook.sdk:FBSDKGraphRequestErrorParsedJSONResponseKey";
#else
NSString *const FBSDKErrorArgumentCollectionKey = @"com.facebook.sdk:FBSDKErrorArgumentCollectionKey";
NSString *const FBSDKErrorArgumentNameKey = @"com.facebook.sdk:FBSDKErrorArgumentNameKey";
NSString *const FBSDKErrorArgumentValueKey = @"com.facebook.sdk:FBSDKErrorArgumentValueKey";
NSString *const FBSDKErrorDeveloperMessageKey = @"com.facebook.sdk:FBSDKErrorDeveloperMessageKey";
NSString *const FBSDKErrorLocalizedDescriptionKey = @"com.facebook.sdk:FBSDKErrorLocalizedDescriptionKey";
NSString *const FBSDKErrorLocalizedTitleKey = @"com.facebook.sdk:FBSDKErrorLocalizedErrorTitleKey";
NSString *const FBSDKGraphRequestErrorKey = @"com.facebook.sdk:FBSDKGraphRequestErrorCategoryKey";
NSString *const FBSDKGraphRequestErrorCategoryKey = @"com.facebook.sdk:FBSDKGraphRequestErrorCategoryKey";
NSString *const FBSDKGraphRequestErrorGraphErrorCodeKey = @"com.facebook.sdk:FBSDKGraphRequestErrorGraphErrorCodeKey";
NSString *const FBSDKGraphRequestErrorGraphErrorSubcodeKey = @"com.facebook.sdk:FBSDKGraphRequestErrorGraphErrorSubcodeKey";
NSString *const FBSDKGraphRequestErrorHTTPStatusCodeKey = @"com.facebook.sdk:FBSDKGraphRequestErrorHTTPStatusCodeKey";
NSString *const FBSDKGraphRequestErrorParsedJSONResponseKey = @"com.facebook.sdk:FBSDKGraphRequestErrorParsedJSONResponseKey";
#endif

View File

@ -0,0 +1,39 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Extension protocol for NSCopying that adds the copy method, which is implemented on NSObject.
NSObject<NSCopying> implicitly conforms to this protocol.
*/
NS_SWIFT_NAME(Copying)
@protocol FBSDKCopying <NSCopying, NSObject>
/**
Implemented by NSObject as a convenience to copyWithZone:.
@return A copy of the receiver.
*/
- (id)copy;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,96 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <UIKit/UIKit.h>
#ifdef BUCK
#import <FBSDKCoreKit/FBSDKAccessToken.h>
#import <FBSDKCoreKit/FBSDKAppEvents.h>
#import <FBSDKCoreKit/FBSDKApplicationDelegate.h>
#import <FBSDKCoreKit/FBSDKButton.h>
#import <FBSDKCoreKit/FBSDKConstants.h>
#import <FBSDKCoreKit/FBSDKCopying.h>
#import <FBSDKCoreKit/FBSDKGraphRequest.h>
#import <FBSDKCoreKit/FBSDKGraphRequestConnection.h>
#import <FBSDKCoreKit/FBSDKGraphRequestDataAttachment.h>
#import <FBSDKCoreKit/FBSDKSettings.h>
#import <FBSDKCoreKit/FBSDKTestUsersManager.h>
#import <FBSDKCoreKit/FBSDKUtility.h>
#if !TARGET_OS_TV
#import <FBSDKCoreKit/FBSDKAppLink.h>
#import <FBSDKCoreKit/FBSDKAppLinkNavigation.h>
#import <FBSDKCoreKit/FBSDKAppLinkResolver.h>
#import <FBSDKCoreKit/FBSDKAppLinkResolving.h>
#import <FBSDKCoreKit/FBSDKAppLinkReturnToRefererController.h>
#import <FBSDKCoreKit/FBSDKAppLinkReturnToRefererView.h>
#import <FBSDKCoreKit/FBSDKAppLinkTarget.h>
#import <FBSDKCoreKit/FBSDKAppLinkUtility.h>
#import <FBSDKCoreKit/FBSDKGraphErrorRecoveryProcessor.h>
#import <FBSDKCoreKit/FBSDKMeasurementEvent.h>
#import <FBSDKCoreKit/FBSDKMutableCopying.h>
#import <FBSDKCoreKit/FBSDKProfile.h>
#import <FBSDKCoreKit/FBSDKProfilePictureView.h>
#import <FBSDKCoreKit/FBSDKURL.h>
#import <FBSDKCoreKit/FBSDKWebViewAppLinkResolver.h>
#else
#import <FBSDKCoreKit/FBSDKDeviceButton.h>
#import <FBSDKCoreKit/FBSDKDeviceViewControllerBase.h>
#endif
#else
#import "FBSDKAccessToken.h"
#import "FBSDKAppEvents.h"
#import "FBSDKApplicationDelegate.h"
#import "FBSDKButton.h"
#import "FBSDKConstants.h"
#import "FBSDKCopying.h"
#import "FBSDKGraphRequest.h"
#import "FBSDKGraphRequestConnection.h"
#import "FBSDKGraphRequestDataAttachment.h"
#import "FBSDKSettings.h"
#import "FBSDKTestUsersManager.h"
#import "FBSDKUtility.h"
#if !TARGET_OS_TV
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkNavigation.h"
#import "FBSDKAppLinkResolver.h"
#import "FBSDKAppLinkResolving.h"
#import "FBSDKAppLinkReturnToRefererController.h"
#import "FBSDKAppLinkReturnToRefererView.h"
#import "FBSDKAppLinkTarget.h"
#import "FBSDKAppLinkUtility.h"
#import "FBSDKGraphErrorRecoveryProcessor.h"
#import "FBSDKMeasurementEvent.h"
#import "FBSDKMutableCopying.h"
#import "FBSDKProfile.h"
#import "FBSDKProfilePictureView.h"
#import "FBSDKURL.h"
#import "FBSDKWebViewAppLinkResolver.h"
#else
#import "FBSDKDeviceButton.h"
#import "FBSDKDeviceViewControllerBase.h"
#endif
#endif
#define FBSDK_VERSION_STRING @"5.15.1"
#define FBSDK_TARGET_PLATFORM_VERSION @"v5.0"

View File

@ -0,0 +1,39 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if TARGET_OS_TV
#import "FBSDKButton.h"
NS_ASSUME_NONNULL_BEGIN
/*
An internal base class for device related flows.
This is an internal API that should not be used directly and is subject to change.
*/
NS_SWIFT_NAME(FBDeviceButton)
@interface FBSDKDeviceButton : FBSDKButton
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,147 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if TARGET_OS_TV
#import "FBSDKDeviceButton.h"
#import <UIKit/UIKit.h>
#import "FBSDKCoreKit+Internal.h"
static const CGFloat kFBLogoSize = 54.0;
static const CGFloat kFBLogoLeftMargin = 36.0;
static const CGFloat kRightMargin = 12.0;
static const CGFloat kPreferredPaddingBetweenLogoTitle = 44.0;
@implementation FBSDKDeviceButton
#pragma mark - Layout
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
[super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
if (self == context.nextFocusedView) {
[coordinator addCoordinatedAnimations:^{
self.transform = CGAffineTransformMakeScale(1.05, 1.05);
self.layer.shadowOpacity = 0.5;
} completion:NULL];
} else if (self == context.previouslyFocusedView) {
[coordinator addCoordinatedAnimations:^{
self.transform = CGAffineTransformMakeScale(1.0, 1.0);
self.layer.shadowOpacity = 0;
} completion:NULL];
}
}
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
CGFloat centerY = CGRectGetMidY(contentRect);
CGFloat y = centerY - (kFBLogoSize / 2.0);
return CGRectMake(kFBLogoLeftMargin, y, kFBLogoSize, kFBLogoSize);
}
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
if (self.hidden || CGRectIsEmpty(self.bounds)) {
return CGRectZero;
}
CGRect imageRect = [self imageRectForContentRect:contentRect];
CGFloat titleX = CGRectGetMaxX(imageRect);
CGRect rect = CGRectMake(titleX, 0, CGRectGetWidth(contentRect) - titleX - kRightMargin, CGRectGetHeight(contentRect));
if (!self.layer.needsLayout) {
CGSize titleSize = [FBSDKMath ceilForSize:[self.titleLabel.attributedText boundingRectWithSize:contentRect.size
options:(NSStringDrawingUsesDeviceMetrics |
NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingUsesFontLeading)
context:NULL].size];
CGFloat titlePadding = ( CGRectGetWidth(rect) - titleSize.width ) / 2;
if (titlePadding > titleX) {
// if there's room to re-center the text, do so.
rect = CGRectMake(kRightMargin, 0, CGRectGetWidth(contentRect) - kRightMargin - kRightMargin, CGRectGetHeight(contentRect));
}
}
return rect;
}
#pragma mark - FBSDKButton
- (UIFont *)defaultFont
{
return [UIFont systemFontOfSize:38];
}
- (CGSize)sizeThatFits:(CGSize)size attributedTitle:(NSAttributedString *)title
{
CGSize titleSize = [FBSDKMath ceilForSize:[title boundingRectWithSize:size
options:(NSStringDrawingUsesDeviceMetrics |
NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingUsesFontLeading)
context:NULL].size];
CGFloat logoAndTitleWidth = kFBLogoSize + kPreferredPaddingBetweenLogoTitle + titleSize.width + kPreferredPaddingBetweenLogoTitle;
CGFloat height = 108;
CGSize contentSize = CGSizeMake(kFBLogoLeftMargin + logoAndTitleWidth + kRightMargin,
height);
return contentSize;
}
- (CGSize)sizeThatFits:(CGSize)size title:(NSString *)title
{
return [self sizeThatFits:size attributedTitle:[self attributedTitleStringFromString:title]];
}
#pragma mark - Subclasses
- (NSAttributedString *)attributedTitleStringFromString:(NSString *)string
{
if (!string) {
return nil;
}
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.alignment = NSTextAlignmentCenter;
style.lineBreakMode = NSLineBreakByClipping;
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc] initWithString:string
attributes:@{
NSParagraphStyleAttributeName: style,
NSFontAttributeName: [self defaultFont],
NSForegroundColorAttributeName: [UIColor whiteColor]
}];
// Now find all the spaces and widen their kerning.
NSRange range = NSMakeRange(0, string.length);
while (range.location != NSNotFound) {
NSRange spaceRange = [string rangeOfString:@" " options:0 range:range];
if (spaceRange.location == NSNotFound) {
break;
}
[attributedString addAttribute:NSKernAttributeName
value:@2.7
range:spaceRange];
range = NSMakeRange(spaceRange.location + 1, string.length - spaceRange.location - 1);
}
return attributedString;
}
@end
#endif

Some files were not shown because too many files have changed in this diff Show More