Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

387 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
// Module includes
#include "OnlineIdentityFacebook.h"
#include "OnlineSharingFacebook.h"
#include "OnlineSubsystemFacebookPrivate.h"
#include "Interfaces/OnlineSharingInterface.h"
#include "Interfaces/OnlineExternalUIInterface.h"
#include "Misc/ConfigCacheIni.h"
#include "IOS/IOSAsyncTask.h"
THIRD_PARTY_INCLUDES_START
#if UE_WITH_CLASSIC_FACEBOOK_LOGIN
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#endif
#import <AuthenticationServices/AuthenticationServices.h>
#import <SafariServices/SafariServices.h>
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <FBSDKCoreKit/FBSDKCoreKit-Swift.h>
#import <FBSDKLoginKit/FBSDKLoginKit-Swift.h>
THIRD_PARTY_INCLUDES_END
///////////////////////////////////////////////////////////////////////////////////////
// FOnlineIdentityFacebook implementation
static TOptional<bool> ShouldUseClassicLogin()
{
bool bUseClassicLogin = true;
bool bFallbackToLimitedLogin = false;
GConfig->GetBool(TEXT("OnlineSubsystemFacebook"), TEXT("bUseClassicLogin"), bUseClassicLogin, GEngineIni);
GConfig->GetBool(TEXT("OnlineSubsystemFacebook"), TEXT("bFallbackToLimitedLogin"), bFallbackToLimitedLogin, GEngineIni);
bool bClassicLoginAllowed = false;
#if UE_WITH_CLASSIC_FACEBOOK_LOGIN
switch(ATTrackingManager.trackingAuthorizationStatus)
{
case ATTrackingManagerAuthorizationStatusAuthorized:
bClassicLoginAllowed = true;
break;
case ATTrackingManagerAuthorizationStatusDenied:
case ATTrackingManagerAuthorizationStatusNotDetermined:
case ATTrackingManagerAuthorizationStatusRestricted:
bClassicLoginAllowed = false;
break;
}
#endif
if (bUseClassicLogin && !bClassicLoginAllowed)
{
if (bFallbackToLimitedLogin)
{
bUseClassicLogin = false;
UE_LOG_ONLINE_IDENTITY(Warning, TEXT("Falling back to Limited Facebook login because application tracking was not authorized"));
}
else
{
UE_LOG_ONLINE_IDENTITY(Error, TEXT("Classic Facebook login is not supported if application tracking was not authorized"));
return NullOpt;
}
}
return bUseClassicLogin;
}
FOnlineIdentityFacebook::FOnlineIdentityFacebook(FOnlineSubsystemFacebook* InSubsystem)
: FOnlineIdentityFacebookCommon(InSubsystem)
{
// Setup scopes fields
GConfig->GetArray(TEXT("OnlineSubsystemFacebook.OnlineIdentityFacebook"), TEXT("ScopeFields"), ScopeFields, GEngineIni);
// always required fields
ScopeFields.AddUnique(TEXT(PERM_PUBLIC_PROFILE));
}
void FOnlineIdentityFacebook::Init()
{
FacebookHelper = [[FFacebookHelper alloc] initWithOwner: AsShared()];
}
void FOnlineIdentityFacebook::Shutdown()
{
[FacebookHelper Shutdown];
FacebookHelper = nil;
}
void FOnlineIdentityFacebook::OnFacebookTokenChange(FBSDKAccessToken* OldToken, FBSDKAccessToken* NewToken)
{
UE_LOG_ONLINE_IDENTITY(Warning, TEXT("FOnlineIdentityFacebook::OnFacebookTokenChange Old: %p New: %p"), OldToken, NewToken);
}
void FOnlineIdentityFacebook::OnFacebookUserIdChange()
{
UE_LOG_ONLINE_IDENTITY(Warning, TEXT("FOnlineIdentityFacebook::OnFacebookUserIdChange"));
}
void FOnlineIdentityFacebook::OnFacebookProfileChange(FBSDKProfile* OldProfile, FBSDKProfile* NewProfile)
{
UE_LOG_ONLINE_IDENTITY(Warning, TEXT("FOnlineIdentityFacebook::OnFacebookProfileChange Old: %p New: %p"), OldProfile, NewProfile);
}
bool FOnlineIdentityFacebook::IsUsingClassicLogin() const
{
return bIsUsingClassicLogin;
}
bool FOnlineIdentityFacebook::Login(int32 LocalUserNum, const FOnlineAccountCredentials& AccountCredentials)
{
bool bTriggeredLogin = true;
if (bIsLoginInProgress)
{
TriggerOnLoginCompleteDelegates(LocalUserNum, false, *FUniqueNetIdFacebook::EmptyId(), TEXT("Login already in progress"));
return false;
}
if (GetLoginStatus(LocalUserNum) != ELoginStatus::NotLoggedIn)
{
TriggerOnLoginCompleteDelegates(LocalUserNum, true, *GetUniquePlayerId(LocalUserNum), TEXT("Already logged in"));
return false;
}
TOptional<bool> UseClassicLogin = ShouldUseClassicLogin();
if (!UseClassicLogin.IsSet())
{
TriggerOnLoginCompleteDelegates(LocalUserNum, false, *FUniqueNetIdFacebook::EmptyId(), TEXT("Login type unsupported"));
return false;
}
bIsLoginInProgress = true;
bIsUsingClassicLogin = *UseClassicLogin;
dispatch_async(dispatch_get_main_queue(),^
{
if ((bIsUsingClassicLogin && (FBSDKAccessToken.currentAccessToken == nil || [FBSDKAccessToken.currentAccessToken isExpired])) ||
(!bIsUsingClassicLogin && FBSDKProfile.currentProfile == nil) )
{
FBSDKLoginManager* loginManager = [[FBSDKLoginManager alloc] init];
NSMutableArray* Permissions = [[NSMutableArray alloc] initWithCapacity:ScopeFields.Num()];
for (int32 ScopeIdx = 0; ScopeIdx < ScopeFields.Num(); ScopeIdx++)
{
NSString* ScopeStr = [NSString stringWithFString:ScopeFields[ScopeIdx]];
[Permissions addObject: ScopeStr];
}
FBSDKLoginConfiguration *Configuration = [[FBSDKLoginConfiguration alloc] initWithPermissions: Permissions
tracking: bIsUsingClassicLogin? FBSDKLoginTrackingEnabled : FBSDKLoginTrackingLimited];
[loginManager logInFromViewController: nil
configuration: Configuration
completion: ^(FBSDKLoginManagerLoginResult* result, NSError* error)
{
UE_LOG_ONLINE_IDENTITY(Display, TEXT("[FBSDKLoginManager logInFromViewController]"));
bool bSuccessfulLogin = false;
FString ErrorStr;
if(error)
{
ErrorStr = FString::Printf(TEXT("[%d] %s"), [error code], *FString([error localizedDescription]));
UE_LOG_ONLINE_IDENTITY(Display, TEXT("[FBSDKLoginManager logInFromViewController = %s]"), *ErrorStr);
}
else if(result.isCancelled)
{
ErrorStr = LOGIN_CANCELLED;
UE_LOG_ONLINE_IDENTITY(Display, TEXT("[FBSDKLoginManager logInFromViewController = cancelled"));
}
else
{
UE_LOG_ONLINE_IDENTITY(Display, TEXT("[FBSDKLoginManager logInFromViewController = true]"));
bSuccessfulLogin = true;
}
TArray<FString> GrantedPermissions, DeclinedPermissions;
GrantedPermissions.Reserve(result.grantedPermissions.count);
for(NSString* permission in result.grantedPermissions)
{
GrantedPermissions.Add(permission);
}
DeclinedPermissions.Reserve(result.declinedPermissions.count);
for(NSString* permission in result.declinedPermissions)
{
DeclinedPermissions.Add(permission);
}
[FAppleAsyncTask CreateTaskWithBlock : ^ bool(void)
{
// Trigger this on the game thread
if (bSuccessfulLogin)
{
TSharedPtr<FOnlineSharingFacebook> Sharing = StaticCastSharedPtr<FOnlineSharingFacebook>(FacebookSubsystem->GetSharingInterface());
if (bIsUsingClassicLogin)
{
const FString AccessToken([[result token] tokenString]);
if (Sharing)
{
Sharing->SetCurrentPermissions(GrantedPermissions, DeclinedPermissions);
}
Login(LocalUserNum, AccessToken);
}
else
{
if (Sharing)
{
Sharing->SetCurrentPermissions(GrantedPermissions, DeclinedPermissions);
}
LoginLimited(LocalUserNum);
}
}
else
{
OnLoginAttemptComplete(LocalUserNum, false, ErrorStr);
}
return true;
}];
}
];
}
else
{
// Skip right to attempting to use the token to query user profile
// or the current profile in case of limited login
// Could fail with an expired auth token (eg. user revoked app)
if (bIsUsingClassicLogin)
{
const FString AccessToken([FBSDKAccessToken.currentAccessToken tokenString]);
[FAppleAsyncTask CreateTaskWithBlock : ^ bool(void)
{
Login(LocalUserNum, FString(AccessToken));
return true;
}];
}
else
{
[FAppleAsyncTask CreateTaskWithBlock : ^ bool(void)
{
LoginLimited(LocalUserNum);
return true;
}];
}
}
}
);
return bTriggeredLogin;
}
void FOnlineIdentityFacebook::Login(int32 LocalUserNum, const FString& AccessToken)
{
FOnProfileRequestComplete CompletionDelegate = FOnProfileRequestComplete::CreateLambda([this](int32 LocalUserNumFromRequest, bool bWasProfileRequestSuccessful, const FString& ErrorStr)
{
OnLoginAttemptComplete(LocalUserNumFromRequest, bWasProfileRequestSuccessful, ErrorStr);
});
ProfileRequest(LocalUserNum, AccessToken, ProfileFields, CompletionDelegate);
}
void FOnlineIdentityFacebook::LoginLimited(int32 LocalUserNum)
{
// Gather data from profile snapshot and store it
TSharedRef<FUserOnlineAccountFacebookIOS> User = MakeShared<FUserOnlineAccountFacebookIOS>(FBSDKProfile.currentProfile);
UserAccounts.Add(User->GetUserId()->ToString(), User);
UserIds.Add(LocalUserNum, User->GetUserId());
OnLoginAttemptComplete(LocalUserNum, true, TEXT(""));
}
FString FOnlineIdentityFacebook::GetAuthToken(int32 LocalUserNum) const
{
if (IsUsingClassicLogin())
{
return FOnlineIdentityFacebookCommon::GetAuthToken(LocalUserNum);
}
FUniqueNetIdPtr UserId = GetUniquePlayerId(LocalUserNum);
if (UserId.IsValid())
{
TSharedPtr<FUserOnlineAccount> UserAccount = GetUserAccount(*UserId);
if (UserAccount.IsValid())
{
FString AuthToken;
if (UserAccount->GetAuthAttribute(AUTH_ATTR_ID_TOKEN, AuthToken))
{
return AuthToken;
}
}
}
return FString();
}
void FOnlineIdentityFacebook::OnLoginAttemptComplete(int32 LocalUserNum, bool bSucceeded, const FString& ErrorStr)
{
LoginStatus = bSucceeded ? ELoginStatus::LoggedIn : ELoginStatus::NotLoggedIn;
if (LoginStatus == ELoginStatus::LoggedIn)
{
UE_LOG_ONLINE_IDENTITY(Display, TEXT("Facebook login was successful"));
FUniqueNetIdPtr UserId = GetUniquePlayerId(LocalUserNum);
check(UserId.IsValid());
bIsLoginInProgress = false;
TriggerOnLoginCompleteDelegates(LocalUserNum, true, *UserId, ErrorStr);
TriggerOnLoginStatusChangedDelegates(LocalUserNum, ELoginStatus::NotLoggedIn, ELoginStatus::LoggedIn, *UserId);
}
else
{
const FString NewErrorStr(ErrorStr);
// Clean up anything left behind from cached access tokens
dispatch_async(dispatch_get_main_queue(),^
{
FBSDKLoginManager* loginManager = [[FBSDKLoginManager alloc] init];
[loginManager logOut];
[FAppleAsyncTask CreateTaskWithBlock : ^ bool(void)
{
// Trigger this on the game thread
UE_LOG_ONLINE_IDENTITY(Display, TEXT("Facebook login failed: %s"), *NewErrorStr);
FUniqueNetIdPtr UserId = GetUniquePlayerId(LocalUserNum);
if (UserId.IsValid())
{
// remove cached user account
UserAccounts.Remove(UserId->ToString());
}
else
{
UserId = FUniqueNetIdFacebook::EmptyId();
}
// remove cached user id
UserIds.Remove(LocalUserNum);
bIsLoginInProgress = false;
TriggerOnLoginCompleteDelegates(LocalUserNum, false, *UserId, NewErrorStr);
return true;
}];
});
}
}
bool FOnlineIdentityFacebook::Logout(int32 LocalUserNum)
{
if (LoginStatus == ELoginStatus::LoggedIn)
{
dispatch_async(dispatch_get_main_queue(),^
{
FBSDKLoginManager* loginManager = [[FBSDKLoginManager alloc] init];
[loginManager logOut];
[FAppleAsyncTask CreateTaskWithBlock : ^ bool(void)
{
// Trigger this on the game thread
FUniqueNetIdPtr UserId = GetUniquePlayerId(LocalUserNum);
if (UserId.IsValid())
{
// remove cached user account
UserAccounts.Remove(UserId->ToString());
}
else
{
UserId = FUniqueNetIdFacebook::EmptyId();
}
// remove cached user id
UserIds.Remove(LocalUserNum);
FacebookSubsystem->ExecuteNextTick([this, UserId, LocalUserNum]() {
LoginStatus = ELoginStatus::NotLoggedIn;
TriggerOnLogoutCompleteDelegates(LocalUserNum, true);
TriggerOnLoginStatusChangedDelegates(LocalUserNum, ELoginStatus::LoggedIn, ELoginStatus::NotLoggedIn, *UserId);
});
return true;
}];
});
}
else
{
UE_LOG_ONLINE_IDENTITY(Warning, TEXT("No logged in user found for LocalUserNum=%d."), LocalUserNum);
FacebookSubsystem->ExecuteNextTick([this, LocalUserNum](){
TriggerOnLogoutCompleteDelegates(LocalUserNum, false);
});
}
return true;
}