/FBSDKCoreKit/FBSDKCoreKit/Internal/FBSDKInternalUtility.m

http://github.com/facebook/facebook-ios-sdk · Objective C · 661 lines · 553 code · 75 blank · 33 comment · 105 complexity · d39ad3c80e8f68f63794d3f3ae6c7591 MD5 · raw file

  1. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  2. //
  3. // You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
  4. // copy, modify, and distribute this software in source code or binary form for use
  5. // in connection with the web services and APIs provided by Facebook.
  6. //
  7. // As with any software that integrates with the Facebook platform, your use of
  8. // this software is subject to the Facebook Developer Principles and Policies
  9. // [http://developers.facebook.com/policy/]. This copyright notice shall be
  10. // included in all copies or substantial portions of the software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  14. // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  15. // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  16. // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  17. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  18. #import "FBSDKInternalUtility.h"
  19. #import <sys/time.h>
  20. #import <mach-o/dyld.h>
  21. #import "FBSDKCoreKit+Internal.h"
  22. #import "FBSDKError.h"
  23. #import "FBSDKSettings+Internal.h"
  24. #import "FBSDKSettings.h"
  25. typedef NS_ENUM(NSUInteger, FBSDKInternalUtilityVersionMask)
  26. {
  27. FBSDKInternalUtilityMajorVersionMask = 0xFFFF0000,
  28. //FBSDKInternalUtilityMinorVersionMask = 0x0000FF00, // unused
  29. //FBSDKInternalUtilityPatchVersionMask = 0x000000FF, // unused
  30. };
  31. typedef NS_ENUM(NSUInteger, FBSDKInternalUtilityVersionShift)
  32. {
  33. FBSDKInternalUtilityMajorVersionShift = 16,
  34. //FBSDKInternalUtilityMinorVersionShift = 8, // unused
  35. //FBSDKInternalUtilityPatchVersionShift = 0, // unused
  36. };
  37. @implementation FBSDKInternalUtility
  38. static BOOL ShouldOverrideHostWithGamingDomain(NSString *hostPrefix) {
  39. return
  40. [[FBSDKAccessToken currentAccessToken] respondsToSelector:@selector(graphDomain)] &&
  41. [[FBSDKAccessToken currentAccessToken].graphDomain isEqualToString:@"gaming"] &&
  42. ([hostPrefix isEqualToString:@"graph."] || [hostPrefix isEqualToString:@"graph-video."]);
  43. }
  44. #pragma mark - Class Methods
  45. + (NSString *)appURLScheme
  46. {
  47. NSString *appID = ([FBSDKSettings appID] ?: @"");
  48. NSString *suffix = ([FBSDKSettings appURLSchemeSuffix] ?: @"");
  49. return [[NSString alloc] initWithFormat: @"fb%@%@", appID, suffix];
  50. }
  51. + (NSURL *)appURLWithHost:(NSString *)host
  52. path:(NSString *)path
  53. queryParameters:(NSDictionary *)queryParameters
  54. error:(NSError *__autoreleasing *)errorRef
  55. {
  56. return [self URLWithScheme:[self appURLScheme]
  57. host:host
  58. path:path
  59. queryParameters:queryParameters
  60. error:errorRef];
  61. }
  62. + (NSDictionary *)dictionaryFromFBURL:(NSURL *)url
  63. {
  64. // version 3.2.3 of the Facebook app encodes the parameters in the query but
  65. // version 3.3 and above encode the parameters in the fragment;
  66. // merge them together with fragment taking priority.
  67. NSMutableDictionary *params = [NSMutableDictionary dictionary];
  68. [params addEntriesFromDictionary:[FBSDKBasicUtility dictionaryWithQueryString:url.query]];
  69. // Only get the params from the fragment if it has authorize as the host
  70. if ([url.host isEqualToString:@"authorize"]) {
  71. [params addEntriesFromDictionary:[FBSDKBasicUtility dictionaryWithQueryString:url.fragment]];
  72. }
  73. return params;
  74. }
  75. + (NSBundle *)bundleForStrings
  76. {
  77. static NSBundle *bundle;
  78. static dispatch_once_t onceToken;
  79. dispatch_once(&onceToken, ^{
  80. NSString *stringsBundlePath = [[NSBundle bundleForClass:[FBSDKApplicationDelegate class]]
  81. pathForResource:@"FacebookSDKStrings"
  82. ofType:@"bundle"];
  83. bundle = [NSBundle bundleWithPath:stringsBundlePath] ?: [NSBundle mainBundle];
  84. });
  85. return bundle;
  86. }
  87. + (uint64_t)currentTimeInMilliseconds
  88. {
  89. struct timeval time;
  90. gettimeofday(&time, NULL);
  91. return ((uint64_t)time.tv_sec * 1000) + (time.tv_usec / 1000);
  92. }
  93. + (void)extractPermissionsFromResponse:(NSDictionary *)responseObject
  94. grantedPermissions:(NSMutableSet *)grantedPermissions
  95. declinedPermissions:(NSMutableSet *)declinedPermissions
  96. expiredPermissions:(NSMutableSet *)expiredPermissions
  97. {
  98. NSArray *resultData = responseObject[@"data"];
  99. if (resultData.count > 0) {
  100. for (NSDictionary *permissionsDictionary in resultData) {
  101. NSString *permissionName = permissionsDictionary[@"permission"];
  102. NSString *status = permissionsDictionary[@"status"];
  103. if ([status isEqualToString:@"granted"]) {
  104. [grantedPermissions addObject:permissionName];
  105. } else if ([status isEqualToString:@"declined"]) {
  106. [declinedPermissions addObject:permissionName];
  107. } else if ([status isEqualToString:@"expired"]) {
  108. [expiredPermissions addObject:permissionName];
  109. }
  110. }
  111. }
  112. }
  113. + (NSURL *)facebookURLWithHostPrefix:(NSString *)hostPrefix
  114. path:(NSString *)path
  115. queryParameters:(NSDictionary *)queryParameters
  116. error:(NSError *__autoreleasing *)errorRef
  117. {
  118. return [self facebookURLWithHostPrefix:hostPrefix
  119. path:path
  120. queryParameters:queryParameters
  121. defaultVersion:@""
  122. error:errorRef];
  123. }
  124. + (NSURL *)facebookURLWithHostPrefix:(NSString *)hostPrefix
  125. path:(NSString *)path
  126. queryParameters:(NSDictionary *)queryParameters
  127. defaultVersion:(NSString *)defaultVersion
  128. error:(NSError *__autoreleasing *)errorRef
  129. {
  130. if (hostPrefix.length && ![hostPrefix hasSuffix:@"."]) {
  131. hostPrefix = [hostPrefix stringByAppendingString:@"."];
  132. }
  133. NSString *host =
  134. ShouldOverrideHostWithGamingDomain(hostPrefix)
  135. ? @"fb.gg"
  136. : @"facebook.com";
  137. NSString *domainPart = [FBSDKSettings facebookDomainPart];
  138. if (domainPart.length) {
  139. host = [[NSString alloc] initWithFormat:@"%@.%@", domainPart, host];
  140. }
  141. host = [NSString stringWithFormat:@"%@%@", hostPrefix ?: @"", host ?: @""];
  142. NSString *version = (defaultVersion.length > 0) ? defaultVersion : [FBSDKSettings graphAPIVersion];
  143. if (version.length) {
  144. version = [@"/" stringByAppendingString:version];
  145. }
  146. if (path.length) {
  147. NSScanner *versionScanner = [[NSScanner alloc] initWithString:path];
  148. if ([versionScanner scanString:@"/v" intoString:NULL] &&
  149. [versionScanner scanInteger:NULL] &&
  150. [versionScanner scanString:@"." intoString:NULL] &&
  151. [versionScanner scanInteger:NULL]) {
  152. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
  153. logEntry:[NSString stringWithFormat:@"Invalid Graph API version:%@, assuming %@ instead",
  154. version,
  155. [FBSDKSettings graphAPIVersion]]];
  156. version = nil;
  157. }
  158. if (![path hasPrefix:@"/"]) {
  159. path = [@"/" stringByAppendingString:path];
  160. }
  161. }
  162. path = [[NSString alloc] initWithFormat:@"%@%@", version ?: @"", path ?: @""];
  163. return [self URLWithScheme:@"https"
  164. host:host
  165. path:path
  166. queryParameters:queryParameters
  167. error:errorRef];
  168. }
  169. + (BOOL)isBrowserURL:(NSURL *)URL
  170. {
  171. NSString *scheme = URL.scheme.lowercaseString;
  172. return ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]);
  173. }
  174. + (BOOL)isFacebookBundleIdentifier:(NSString *)bundleIdentifier
  175. {
  176. return ([bundleIdentifier hasPrefix:@"com.facebook."] ||
  177. [bundleIdentifier hasPrefix:@".com.facebook."]);
  178. }
  179. + (BOOL)isOSRunTimeVersionAtLeast:(NSOperatingSystemVersion)version
  180. {
  181. return ([self _compareOperatingSystemVersion:[self operatingSystemVersion] toVersion:version] != NSOrderedAscending);
  182. }
  183. + (BOOL)isSafariBundleIdentifier:(NSString *)bundleIdentifier
  184. {
  185. return ([bundleIdentifier isEqualToString:@"com.apple.mobilesafari"] ||
  186. [bundleIdentifier isEqualToString:@"com.apple.SafariViewService"]);
  187. }
  188. + (BOOL)isUIKitLinkTimeVersionAtLeast:(FBSDKUIKitVersion)version
  189. {
  190. static int32_t linkTimeMajorVersion;
  191. static dispatch_once_t getVersionOnce;
  192. dispatch_once(&getVersionOnce, ^{
  193. int32_t linkTimeVersion = NSVersionOfLinkTimeLibrary("UIKit");
  194. linkTimeMajorVersion = [self getMajorVersionFromFullLibraryVersion:linkTimeVersion];
  195. });
  196. return (version <= linkTimeMajorVersion);
  197. }
  198. + (BOOL)isUIKitRunTimeVersionAtLeast:(FBSDKUIKitVersion)version
  199. {
  200. static int32_t runTimeMajorVersion;
  201. static dispatch_once_t getVersionOnce;
  202. dispatch_once(&getVersionOnce, ^{
  203. int32_t runTimeVersion = NSVersionOfRunTimeLibrary("UIKit");
  204. runTimeMajorVersion = [self getMajorVersionFromFullLibraryVersion:runTimeVersion];
  205. });
  206. return (version <= runTimeMajorVersion);
  207. }
  208. + (int32_t)getMajorVersionFromFullLibraryVersion:(int32_t)version
  209. {
  210. // Negative values returned by NSVersionOfRunTimeLibrary/NSVersionOfLinkTimeLibrary
  211. // are still valid version numbers, as long as it's not -1.
  212. // After bitshift by 16, the negatives become valid positive major version number.
  213. // We ran into this first time with iOS 12.
  214. if (version != -1) {
  215. return ((version & FBSDKInternalUtilityMajorVersionMask) >> FBSDKInternalUtilityMajorVersionShift);
  216. } else {
  217. return 0;
  218. }
  219. }
  220. + (BOOL)object:(id)object isEqualToObject:(id)other
  221. {
  222. if (object == other) {
  223. return YES;
  224. }
  225. if (!object || !other) {
  226. return NO;
  227. }
  228. return [object isEqual:other];
  229. }
  230. + (NSOperatingSystemVersion)operatingSystemVersion
  231. {
  232. static NSOperatingSystemVersion operatingSystemVersion = {
  233. .majorVersion = 0,
  234. .minorVersion = 0,
  235. .patchVersion = 0,
  236. };
  237. static dispatch_once_t getVersionOnce;
  238. dispatch_once(&getVersionOnce, ^{
  239. if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
  240. operatingSystemVersion = [NSProcessInfo processInfo].operatingSystemVersion;
  241. } else {
  242. NSArray *components = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:@"."];
  243. switch (components.count) {
  244. default:
  245. case 3:
  246. operatingSystemVersion.patchVersion = [components[2] integerValue];
  247. // fall through
  248. case 2:
  249. operatingSystemVersion.minorVersion = [components[1] integerValue];
  250. // fall through
  251. case 1:
  252. operatingSystemVersion.majorVersion = [components[0] integerValue];
  253. break;
  254. case 0:
  255. operatingSystemVersion.majorVersion = ([self isUIKitLinkTimeVersionAtLeast:FBSDKUIKitVersion_7_0] ? 7 : 6);
  256. break;
  257. }
  258. }
  259. });
  260. return operatingSystemVersion;
  261. }
  262. + (BOOL)shouldManuallyAdjustOrientation
  263. {
  264. return (![self isUIKitLinkTimeVersionAtLeast:FBSDKUIKitVersion_8_0] ||
  265. ![self isUIKitRunTimeVersionAtLeast:FBSDKUIKitVersion_8_0]);
  266. }
  267. + (NSURL *)URLWithScheme:(NSString *)scheme
  268. host:(NSString *)host
  269. path:(NSString *)path
  270. queryParameters:(NSDictionary *)queryParameters
  271. error:(NSError *__autoreleasing *)errorRef
  272. {
  273. if (![path hasPrefix:@"/"]) {
  274. path = [@"/" stringByAppendingString:path ?: @""];
  275. }
  276. NSString *queryString = nil;
  277. if (queryParameters.count) {
  278. NSError *queryStringError;
  279. queryString = [@"?" stringByAppendingString:[FBSDKBasicUtility queryStringWithDictionary:queryParameters
  280. error:&queryStringError
  281. invalidObjectHandler:NULL]];
  282. if (!queryString) {
  283. if (errorRef != NULL) {
  284. *errorRef = [FBSDKError invalidArgumentErrorWithName:@"queryParameters"
  285. value:queryParameters
  286. message:nil
  287. underlyingError:queryStringError];
  288. }
  289. return nil;
  290. }
  291. }
  292. NSURL *const URL = [NSURL URLWithString:[NSString stringWithFormat:
  293. @"%@://%@%@%@",
  294. scheme ?: @"",
  295. host ?: @"",
  296. path ?: @"",
  297. queryString ?: @""]];
  298. if (errorRef != NULL) {
  299. if (URL) {
  300. *errorRef = nil;
  301. } else {
  302. *errorRef = [FBSDKError unknownErrorWithMessage:@"Unknown error building URL."];
  303. }
  304. }
  305. return URL;
  306. }
  307. + (void)deleteFacebookCookies
  308. {
  309. NSHTTPCookieStorage *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  310. NSArray *facebookCookies = [cookies cookiesForURL:[self facebookURLWithHostPrefix:@"m."
  311. path:@"/dialog/"
  312. queryParameters:@{}
  313. error:NULL]];
  314. for (NSHTTPCookie *cookie in facebookCookies) {
  315. [cookies deleteCookie:cookie];
  316. }
  317. }
  318. static NSMapTable *_transientObjects;
  319. + (void)registerTransientObject:(id)object
  320. {
  321. NSAssert([NSThread isMainThread], @"Must be called from the main thread!");
  322. if (!_transientObjects) {
  323. _transientObjects = [[NSMapTable alloc] init];
  324. }
  325. NSUInteger count = ((NSNumber *)[_transientObjects objectForKey:object]).unsignedIntegerValue;
  326. [_transientObjects setObject:@(count + 1) forKey:object];
  327. }
  328. + (void)unregisterTransientObject:(__weak id)object
  329. {
  330. if (!object) {
  331. return;
  332. }
  333. NSAssert([NSThread isMainThread], @"Must be called from the main thread!");
  334. NSUInteger count = ((NSNumber *)[_transientObjects objectForKey:object]).unsignedIntegerValue;
  335. if (count == 1) {
  336. [_transientObjects removeObjectForKey:object];
  337. } else if (count != 0) {
  338. [_transientObjects setObject:@(count - 1) forKey:object];
  339. } else {
  340. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
  341. formatString:@"unregisterTransientObject:%@ count is 0. This may indicate a bug in the FBSDK. Please"
  342. " file a report to developers.facebook.com/bugs if you encounter any problems. Thanks!", [object class]];
  343. }
  344. }
  345. + (UIViewController *)viewControllerForView:(UIView *)view
  346. {
  347. UIResponder *responder = view.nextResponder;
  348. while (responder) {
  349. if ([responder isKindOfClass:[UIViewController class]]) {
  350. return (UIViewController *)responder;
  351. }
  352. responder = responder.nextResponder;
  353. }
  354. return nil;
  355. }
  356. #pragma mark - FB Apps Installed
  357. + (BOOL)isFacebookAppInstalled
  358. {
  359. static dispatch_once_t onceToken;
  360. dispatch_once(&onceToken, ^{
  361. [FBSDKInternalUtility checkRegisteredCanOpenURLScheme:FBSDK_CANOPENURL_FACEBOOK];
  362. });
  363. return [self _canOpenURLScheme:FBSDK_CANOPENURL_FACEBOOK];
  364. }
  365. + (BOOL)isMessengerAppInstalled
  366. {
  367. static dispatch_once_t onceToken;
  368. dispatch_once(&onceToken, ^{
  369. [FBSDKInternalUtility checkRegisteredCanOpenURLScheme:FBSDK_CANOPENURL_MESSENGER];
  370. });
  371. return [self _canOpenURLScheme:FBSDK_CANOPENURL_MESSENGER];
  372. }
  373. + (BOOL)isMSQRDPlayerAppInstalled
  374. {
  375. static dispatch_once_t onceToken;
  376. dispatch_once(&onceToken, ^{
  377. [FBSDKInternalUtility checkRegisteredCanOpenURLScheme:FBSDK_CANOPENURL_MSQRD_PLAYER];
  378. });
  379. return [self _canOpenURLScheme:FBSDK_CANOPENURL_MSQRD_PLAYER];
  380. }
  381. #pragma mark - Helper Methods
  382. + (NSComparisonResult)_compareOperatingSystemVersion:(NSOperatingSystemVersion)version1
  383. toVersion:(NSOperatingSystemVersion)version2
  384. {
  385. if (version1.majorVersion < version2.majorVersion) {
  386. return NSOrderedAscending;
  387. } else if (version1.majorVersion > version2.majorVersion) {
  388. return NSOrderedDescending;
  389. } else if (version1.minorVersion < version2.minorVersion) {
  390. return NSOrderedAscending;
  391. } else if (version1.minorVersion > version2.minorVersion) {
  392. return NSOrderedDescending;
  393. } else if (version1.patchVersion < version2.patchVersion) {
  394. return NSOrderedAscending;
  395. } else if (version1.patchVersion > version2.patchVersion) {
  396. return NSOrderedDescending;
  397. } else {
  398. return NSOrderedSame;
  399. }
  400. }
  401. + (BOOL)_canOpenURLScheme:(NSString *)scheme
  402. {
  403. NSURLComponents *components = [[NSURLComponents alloc] init];
  404. components.scheme = scheme;
  405. components.path = @"/";
  406. return [[UIApplication sharedApplication] canOpenURL:components.URL];
  407. }
  408. + (void)validateAppID
  409. {
  410. if (![FBSDKSettings appID]) {
  411. NSString *reason = @"App ID not found. Add a string value with your app ID for the key "
  412. @"FacebookAppID to the Info.plist or call [FBSDKSettings setAppID:].";
  413. @throw [NSException exceptionWithName:@"InvalidOperationException" reason:reason userInfo:nil];
  414. }
  415. }
  416. + (NSString *)validateRequiredClientAccessToken {
  417. if (![FBSDKSettings clientToken]) {
  418. NSString *reason = @"ClientToken is required to be set for this operation. "
  419. @"Set the FacebookClientToken in the Info.plist or call [FBSDKSettings setClientToken:]. "
  420. @"You can find your client token in your App Settings -> Advanced.";
  421. @throw [NSException exceptionWithName:@"InvalidOperationException" reason:reason userInfo:nil];
  422. }
  423. return [NSString stringWithFormat:@"%@|%@", [FBSDKSettings appID], [FBSDKSettings clientToken]];
  424. }
  425. + (void)validateURLSchemes
  426. {
  427. [self validateAppID];
  428. NSString *defaultUrlScheme = [NSString stringWithFormat:@"fb%@%@", [FBSDKSettings appID], [FBSDKSettings appURLSchemeSuffix] ?: @""];
  429. if (![self isRegisteredURLScheme:defaultUrlScheme]) {
  430. NSString *reason = [NSString stringWithFormat:@"%@ is not registered as a URL scheme. Please add it in your Info.plist", defaultUrlScheme];
  431. @throw [NSException exceptionWithName:@"InvalidOperationException" reason:reason userInfo:nil];
  432. }
  433. }
  434. + (void)validateFacebookReservedURLSchemes
  435. {
  436. for (NSString * fbUrlScheme in @[FBSDK_CANOPENURL_FACEBOOK, FBSDK_CANOPENURL_MESSENGER, FBSDK_CANOPENURL_FBAPI, FBSDK_CANOPENURL_SHARE_EXTENSION]) {
  437. if ([self isRegisteredURLScheme:fbUrlScheme]) {
  438. NSString *reason = [NSString stringWithFormat:@"%@ is registered as a URL scheme. Please move the entry from CFBundleURLSchemes in your Info.plist to LSApplicationQueriesSchemes. If you are trying to resolve \"canOpenURL: failed\" warnings, those only indicate that the Facebook app is not installed on your device or simulator and can be ignored.", fbUrlScheme];
  439. @throw [NSException exceptionWithName:@"InvalidOperationException" reason:reason userInfo:nil];
  440. }
  441. }
  442. }
  443. + (UIWindow *)findWindow
  444. {
  445. #pragma clang diagnostic push
  446. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  447. UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
  448. #pragma clang diagnostic pop
  449. if (topWindow == nil || topWindow.windowLevel < UIWindowLevelNormal) {
  450. for (UIWindow *window in [UIApplication sharedApplication].windows) {
  451. if (window.windowLevel >= topWindow.windowLevel && !window.isHidden) {
  452. topWindow = window;
  453. }
  454. }
  455. }
  456. if (topWindow != nil) {
  457. return topWindow;
  458. }
  459. // Find active key window from UIScene
  460. if (@available(iOS 13.0, tvOS 13, *)) {
  461. NSSet *scenes = [[UIApplication sharedApplication] valueForKey:@"connectedScenes"];
  462. for (id scene in scenes) {
  463. id activationState = [scene valueForKeyPath:@"activationState"];
  464. BOOL isActive = activationState != nil && [activationState integerValue] == 0;
  465. if (isActive) {
  466. Class WindowScene = NSClassFromString(@"UIWindowScene");
  467. if ([scene isKindOfClass:WindowScene]) {
  468. NSArray<UIWindow *> *windows = [scene valueForKeyPath:@"windows"];
  469. for (UIWindow *window in windows) {
  470. if (window.isKeyWindow) {
  471. return window;
  472. } else if (window.windowLevel >= topWindow.windowLevel && !window.isHidden) {
  473. topWindow = window;
  474. }
  475. }
  476. }
  477. }
  478. }
  479. }
  480. if (topWindow == nil) {
  481. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
  482. formatString:@"Unable to find a valid UIWindow", nil];
  483. }
  484. return topWindow;
  485. }
  486. + (UIViewController *)topMostViewController
  487. {
  488. UIWindow *keyWindow = [self findWindow];
  489. // SDK expects a key window at this point, if it is not, make it one
  490. if (keyWindow != nil && !keyWindow.isKeyWindow) {
  491. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
  492. formatString:@"Unable to obtain a key window, marking %@ as keyWindow", keyWindow.description];
  493. [keyWindow makeKeyWindow];
  494. }
  495. UIViewController *topController = keyWindow.rootViewController;
  496. while (topController.presentedViewController) {
  497. topController = topController.presentedViewController;
  498. }
  499. return topController;
  500. }
  501. #if !TARGET_OS_TV
  502. + (UIInterfaceOrientation)statusBarOrientation
  503. {
  504. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
  505. if (@available(iOS 13.0, *)) {
  506. return [self findWindow].windowScene.interfaceOrientation;
  507. } else {
  508. return UIInterfaceOrientationUnknown;
  509. }
  510. #else
  511. return UIApplication.sharedApplication.statusBarOrientation;
  512. #endif
  513. }
  514. #endif
  515. + (NSString *)hexadecimalStringFromData:(NSData *)data
  516. {
  517. NSUInteger dataLength = data.length;
  518. if (dataLength == 0) {
  519. return nil;
  520. }
  521. const unsigned char *dataBuffer = data.bytes;
  522. NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
  523. for (int i = 0; i < dataLength; ++i) {
  524. [hexString appendFormat:@"%02x", dataBuffer[i]];
  525. }
  526. return [hexString copy];
  527. }
  528. + (BOOL)isRegisteredURLScheme:(NSString *)urlScheme {
  529. static dispatch_once_t fetchBundleOnce;
  530. static NSArray *urlTypes = nil;
  531. dispatch_once(&fetchBundleOnce, ^{
  532. urlTypes = [[NSBundle mainBundle].infoDictionary valueForKey:@"CFBundleURLTypes"];
  533. });
  534. for (NSDictionary *urlType in urlTypes) {
  535. NSArray *urlSchemes = [urlType valueForKey:@"CFBundleURLSchemes"];
  536. if ([urlSchemes containsObject:urlScheme]) {
  537. return YES;
  538. }
  539. }
  540. return NO;
  541. }
  542. + (void)checkRegisteredCanOpenURLScheme:(NSString *)urlScheme
  543. {
  544. static dispatch_once_t initCheckedSchemesOnce;
  545. static NSMutableSet *checkedSchemes = nil;
  546. dispatch_once(&initCheckedSchemesOnce, ^{
  547. checkedSchemes = [NSMutableSet set];
  548. });
  549. @synchronized(self) {
  550. if ([checkedSchemes containsObject:urlScheme]) {
  551. return;
  552. } else {
  553. [checkedSchemes addObject:urlScheme];
  554. }
  555. }
  556. if (![self isRegisteredCanOpenURLScheme:urlScheme]){
  557. NSString *reason = [NSString stringWithFormat:@"%@ is missing from your Info.plist under LSApplicationQueriesSchemes and is required for iOS 9.0", urlScheme];
  558. [FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors logEntry:reason];
  559. }
  560. }
  561. + (BOOL)isRegisteredCanOpenURLScheme:(NSString *)urlScheme
  562. {
  563. static dispatch_once_t fetchBundleOnce;
  564. static NSArray *schemes = nil;
  565. dispatch_once(&fetchBundleOnce, ^{
  566. schemes = [[NSBundle mainBundle].infoDictionary valueForKey:@"LSApplicationQueriesSchemes"];
  567. });
  568. return [schemes containsObject:urlScheme];
  569. }
  570. + (BOOL)isPublishPermission:(NSString *)permission
  571. {
  572. return [permission hasPrefix:@"publish"] ||
  573. [permission hasPrefix:@"manage"] ||
  574. [permission isEqualToString:@"ads_management"] ||
  575. [permission isEqualToString:@"create_event"] ||
  576. [permission isEqualToString:@"rsvp_event"];
  577. }
  578. + (BOOL)isUnity
  579. {
  580. NSString *userAgentSuffix = [FBSDKSettings userAgentSuffix];
  581. if (userAgentSuffix != nil && [userAgentSuffix rangeOfString:@"Unity"].location != NSNotFound) {
  582. return YES;
  583. }
  584. return NO;
  585. }
  586. @end