1// Copyright 2015-present 650 Industries. All rights reserved.
2
3#if __has_include(<EXFacebook/EXFacebook.h>)
4#import "EXScopedFacebook.h"
5#import <FBSDKCoreKit/FBSDKSettings.h>
6#import <ExpoModulesCore/EXAppLifecycleService.h>
7#import <FBSDKCoreKit/FBSDKApplicationDelegate.h>
8
9@interface EXFacebook (ExportedMethods)
10
11- (void)initializeAsync:(NSDictionary *)options
12               resolver:(EXPromiseResolveBlock)resolve
13               rejecter:(EXPromiseRejectBlock)reject;
14
15- (void)logInWithReadPermissionsWithConfig:(NSDictionary *)config
16                                  resolver:(EXPromiseResolveBlock)resolve
17                                  rejecter:(EXPromiseRejectBlock)reject;
18
19- (void)logOutAsync:(EXPromiseResolveBlock)resolve
20           rejecter:(EXPromiseRejectBlock)reject;
21
22- (void)getAuthenticationCredentialAsync:(EXPromiseResolveBlock)resolve
23                   rejecter:(EXPromiseRejectBlock)reject;
24
25- (void)setAutoInitEnabled:(BOOL)enabled
26                  resolver:(EXPromiseResolveBlock)resolve
27                  rejecter:(EXPromiseRejectBlock)reject;
28
29@end
30
31static NSString *AUTO_INIT_KEY = @"autoInitEnabled";
32
33@interface EXScopedFacebook ()
34
35@property (nonatomic, assign) BOOL isInitialized;
36@property (nonatomic, strong) NSString *appId;
37@property (nonatomic, strong) NSString *displayName;
38@property (nonatomic, strong) NSUserDefaults *settings;
39
40@end
41
42// Expo Go-only EXFacebook module, which ensures that Facebook SDK configurations
43// of different projects don't collide.
44
45@implementation EXScopedFacebook : EXFacebook
46
47- (instancetype)initWithScopeKey:(NSString *)scopeKey manifest:(EXManifestsManifest *)manifest
48{
49  if (self = [super init]) {
50    NSString *suiteName = [NSString stringWithFormat:@"%@#%@", NSStringFromClass(self.class), scopeKey];
51    _settings = [[NSUserDefaults alloc] initWithSuiteName:suiteName];
52
53    BOOL hasPreviouslySetAutoInitEnabled = [_settings boolForKey:AUTO_INIT_KEY];
54    BOOL manifestDefinesAutoInitEnabled = manifest.facebookAutoInitEnabled;
55
56    NSString *scopedFacebookAppId = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FacebookAppID"];
57    NSString *manifestFacebookAppId = manifest.facebookAppId;
58
59    if (hasPreviouslySetAutoInitEnabled || manifestDefinesAutoInitEnabled) {
60      // This happens even before the app foregrounds, which mimics
61      // the mechanism behind EXFacebookAppDelegate.
62      // Check for FacebookAppId in case this is a custom client build
63      if (scopedFacebookAppId) {
64        [FBSDKApplicationDelegate initializeSDK:nil];
65        _isInitialized = YES;
66        if (manifestFacebookAppId) {
67          EXLogInfo(@"Overriding Facebook App ID with Expo Go's. To test your own Facebook App ID, you'll need to build a standalone app. Refer to our documentation for more info- https://docs.expo.io/versions/latest/sdk/facebook/");
68        }
69      } else {
70        EXLogWarn(@"FacebookAutoInit is enabled, but no FacebookAppId has been provided. Facebook SDK initialization aborted.");
71      }
72    }
73  }
74  return self;
75}
76
77- (void)initializeAsync:(NSDictionary *)options
78               resolver:(EXPromiseResolveBlock)resolve
79               rejecter:(EXPromiseRejectBlock)reject
80{
81  _isInitialized = YES;
82  if (options[@"appId"]) {
83    EXLogInfo(@"Overriding Facebook App ID with Expo Go's. To test your own Facebook App ID, you'll need to build a standalone app. Refer to our documentation for more info- https://docs.expo.io/versions/latest/sdk/facebook/");
84  }
85
86  NSString *scopedFacebookAppId = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FacebookAppID"];
87
88  NSMutableDictionary *nativeOptions = [NSMutableDictionary dictionaryWithDictionary:options];
89  // Overwrite the incoming app id with the Expo Facebook SDK app id.
90  nativeOptions[@"appId"] = scopedFacebookAppId;
91
92  [super initializeAsync:nativeOptions resolver:resolve rejecter:reject];
93}
94
95- (void)setAutoInitEnabled:(BOOL)enabled resolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject
96{
97  if (enabled) {
98    [_settings setBool:enabled forKey:AUTO_INIT_KEY];
99    // Facebook SDK on iOS is initialized when `setAutoInitEnabled` is called with `YES`.
100    _isInitialized = YES;
101  }
102  [super setAutoInitEnabled:enabled resolver:resolve rejecter:reject];
103}
104
105- (void)logInWithReadPermissionsWithConfig:(NSDictionary *)config resolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject
106{
107  // If the developer didn't initialize the SDK, let them know.
108  if (!_isInitialized) {
109    reject(@"ERR_FACEBOOK_UNINITIALIZED", @"Facebook SDK has not been initialized yet.", nil);
110    return;
111  }
112  [super logInWithReadPermissionsWithConfig:config resolver:resolve rejecter:reject];
113}
114
115- (void)getAuthenticationCredentialAsync:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject
116{
117  // If the developer didn't initialize the SDK, let them know.
118  if (!_isInitialized) {
119    reject(@"ERR_FACEBOOK_UNINITIALIZED", @"Facebook SDK has not been initialized yet.", nil);
120    return;
121  }
122  [super getAuthenticationCredentialAsync:resolve rejecter:reject];
123}
124
125- (void)logOutAsync:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject
126{
127  // If the developer didn't initialize the SDK, let them know.
128  if (!_isInitialized) {
129    reject(@"ERR_FACEBOOK_UNINITIALIZED", @"Facebook SDK has not been initialized yet.", nil);
130    return;
131  }
132  [super logOutAsync:resolve rejecter:reject];
133}
134
135# pragma mark - EXModuleRegistryConsumer
136
137- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
138{
139  [super setModuleRegistry:moduleRegistry];
140
141  id<EXAppLifecycleService> appLifecycleService = [moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleService)];
142  [appLifecycleService registerAppLifecycleListener:self];
143}
144
145# pragma mark - EXAppLifecycleListener
146
147- (void)onAppBackgrounded {
148  // Save SDK settings state
149  _appId = [FBSDKSettings appID];
150  _displayName = [FBSDKSettings displayName];
151  [FBSDKSettings setAppID:nil];
152  [FBSDKSettings setDisplayName:nil];
153}
154
155- (void)onAppForegrounded {
156  // Restore SDK settings state
157  if (_appId) {
158    [FBSDKSettings setAppID:_appId];
159  }
160  if (_displayName) {
161    [FBSDKSettings setDisplayName:_displayName];
162  }
163}
164
165@end
166#endif
167