Files
UnrealEngine/Engine/Plugins/Online/OnlineSubsystemApple/Source/Private/OnlineExternalUIInterfaceApple.cpp
2025-05-18 13:04:45 +08:00

430 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright Epic Games, Inc. All Rights Reserved.
// Module includes
#include "OnlineExternalUIInterfaceApple.h"
#include "OnlineSubsystemApple.h"
#include "OnlineSubsystemAppleAttributes.h"
#include "Misc/ConfigCacheIni.h"
#if PLATFORM_MAC
#elif PLATFORM_IOS || PLATFORM_TVOS
#include "IOS/IOSAppDelegate.h"
#endif
#import <Foundation/Foundation.h>
#if ONLINESUBSYSTEMAPPLE_IDENTITY_ENABLE_SIWA
#define SIWA_SUPPORTED 1
#else
#define SIWA_SUPPORTED 0
#endif
#if SIWA_SUPPORTED
#import <AuthenticationServices/AuthenticationServices.h>
#endif
DECLARE_MULTICAST_DELEGATE_FiveParams(FOnSignInComplete, NSString* /*user*/, NSData* /*authorizationCode*/, NSString* /*email*/, NSPersonNameComponents* /*fullName*/, NSError* /*error*/);
typedef FOnSignInComplete::FDelegate FOnSignInCompleteDelegate;
// Sign in With Apple interface to supply required deleagates for fulfilment of requests
typedef void (*SignInControllerResultCallback)(NSString* user, NSData* authorizationCode, NSString* email, NSPersonNameComponents* fullName, NSError* error);
#if SIWA_SUPPORTED
@interface SignInController : NSObject <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
#else
@interface SignInController : NSObject
#endif
{
class SignInWithAppleBridge* signInWithAppleBridge;
}
- (instancetype)initWithBridge:(class SignInWithAppleBridge*)bridge;
@end
// Bridge SiWA requests and handle callbacks once completed
class SignInWithAppleBridge
{
public:
/**
* Delegate called when a sign in attempt has completed successfully, or failed
*
* @param user - idenitfier for the logged in user, or null if failed
* @param authorizationCode - short-lived token for proof of authorization, or null if failed
* @param email - email address of the logged in user, or null if not in requested scope, user chose not to share or failed
* @param fullName - name components of the logged in user, or null if not in requested scope, user chose not to share or failed
* @param error - if the attempt failed, can be null if no attempt was made or successful
*/
DEFINE_ONLINE_DELEGATE_FIVE_PARAM(OnSignInComplete, NSString* /*user*/, NSData* /*authorizationCode*/, NSString* /*email*/, NSPersonNameComponents* /*fullName*/, NSError* /*error*/);
SignInWithAppleBridge()
{
Controller = [[SignInController alloc] initWithBridge:this];
}
~SignInWithAppleBridge()
{
if (Controller != nullptr)
{
[Controller release];
Controller = nullptr;
}
}
SignInController* GetSignInController() const
{
return Controller;
}
void SignInResultCallback(NSString* user, NSData* authorizationCode, NSString* email, NSPersonNameComponents* fullName, NSError* error)
{
TriggerOnSignInCompleteDelegates(user, authorizationCode, email, fullName, error);
}
private:
SignInController* Controller;
};
@implementation SignInController
- (instancetype)initWithBridge:(class SignInWithAppleBridge*)bridge;
{
self = [super init];
if (self)
{
signInWithAppleBridge = bridge;
}
return self;
}
#if SIWA_SUPPORTED
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller
{
#if PLATFORM_MAC
// TODO: Mark.Fitt Support Mac window access
return nullptr;
#elif PLATFORM_IOS || PLATFORM_TVOS
if ([IOSAppDelegate GetDelegate].Window == nullptr)
{
NSLog(@"authorizationController: presentationAnchorForAuthorizationController: error window is NULL");
}
return [IOSAppDelegate GetDelegate].Window;
#endif
}
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization
{
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]])
{
ASAuthorizationAppleIDCredential* AuthAppleID = (ASAuthorizationAppleIDCredential *)authorization.credential;
//DEBUG:>>
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: identityToken %@", [AuthAppleID identityToken]);
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: authorizationCode %@", [AuthAppleID authorizationCode]);
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: state %@", [AuthAppleID state]);
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: user %@", [AuthAppleID user]);
//Debug NSArray *Scopes = [AuthAppleID authorizedScopes];
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: scopes count %lu", (unsigned long)[Scopes count]);
//Debug for (int i = 0; i < [Scopes count]; ++i) {
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: scopes %@", Scopes[i]);
//Debug }
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: email %@", [AuthAppleID email]);
//Debug NSLog(@"authorizationController: didCompleteWithAuthorization: fullName %@", [AuthAppleID fullName]);
//DEBUG:<<
if (signInWithAppleBridge != nullptr)
{
signInWithAppleBridge->SignInResultCallback([AuthAppleID user], [AuthAppleID identityToken], [AuthAppleID email], [AuthAppleID fullName], nullptr);
}
}
else
{
// Not the response for the requested auth type
if (signInWithAppleBridge != nullptr)
{
signInWithAppleBridge->SignInResultCallback(nullptr, nullptr, nullptr, nullptr, nullptr);
}
}
}
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error
{
if (signInWithAppleBridge != nullptr)
{
signInWithAppleBridge->SignInResultCallback(nullptr, nullptr, nullptr, nullptr, error);
}
}
#endif
@end
// FOnlineExternalUIApple
FOnlineExternalUIApple::FOnlineExternalUIApple(FOnlineSubsystemApple* InSubsystem)
: Subsystem(InSubsystem)
{
check(InSubsystem != nullptr);
SignInBridge = new SignInWithAppleBridge();
// Setup permission scope fields
GConfig->GetArray(TEXT("OnlineSubsystemApple.OnlineIdentityApple"), TEXT("ScopeFields"), ScopeFields, GEngineIni);
//DEBUG:>>
//Debug ScopeFields.AddUnique(TEXT(APPLE_PERM_EMAIL));
//Debug ScopeFields.AddUnique(TEXT(APPLE_PERM_FULLNAME));
//DEBUG:>>
}
FOnlineExternalUIApple::~FOnlineExternalUIApple()
{
if (SignInBridge)
{
delete SignInBridge;
SignInBridge = nullptr;
}
}
bool FOnlineExternalUIApple::ShowLoginUI(const int ControllerIndex, bool bShowOnlineOnly, bool bShowSkipButton, const FOnLoginUIClosedDelegate& Delegate)
{
bool bStarted = false;
FString ErrorStr;
#if SIWA_SUPPORTED
if (ControllerIndex >= 0 && ControllerIndex < MAX_LOCAL_PLAYERS)
{
FOnlineIdentityApplePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityApple>(Subsystem->GetIdentityInterface());
if (IdentityInterface.IsValid())
{
// Is the user already logged in?
if (IdentityInterface->GetLoginStatus(ControllerIndex)!= ELoginStatus::NotLoggedIn)
{
Subsystem->ExecuteNextTick([IdentityInterface, ControllerIndex, Delegate]()
{
Delegate.ExecuteIfBound(IdentityInterface->GetUniquePlayerId(ControllerIndex), ControllerIndex, FOnlineError::Success());
});
bStarted = true;
}
else
{
LoginUIClosedDelegate = Delegate;
// Show the Sign in with Apple login UI
OnSignInCompleteHandle = SignInBridge->AddOnSignInCompleteDelegate_Handle(FOnSignInCompleteDelegate::CreateRaw(this, &FOnlineExternalUIApple::OnSignInComplete, ControllerIndex));
ASAuthorizationAppleIDProvider *Provider = [ASAuthorizationAppleIDProvider new];
ASAuthorizationAppleIDRequest *Request = Provider.createRequest;
if (ScopeFields.Num() > 0)
{
// Define any requested scopes
// NOTE: We will only receieve scopes for the initial login only, once the app is authorised extended details are no longer available
NSMutableArray* Permissions = [NSMutableArray array];
for (int32 ScopeIdx = 0; ScopeIdx < ScopeFields.Num(); ScopeIdx++)
{
if (ScopeFields[ScopeIdx].Equals(TEXT(APPLE_PERM_EMAIL), ESearchCase::CaseSensitive))
{
[Permissions addObject: ASAuthorizationScopeEmail];
}
else if (ScopeFields[ScopeIdx].Equals(TEXT(APPLE_PERM_FULLNAME), ESearchCase::CaseSensitive))
{
[Permissions addObject: ASAuthorizationScopeFullName];
}
else
{
UE_LOG_ONLINE_IDENTITY(Warning, TEXT("Login invalid scope. %s"), *ScopeFields[ScopeIdx]);
}
}
Request.requestedScopes = Permissions;
}
ASAuthorizationController *Controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[Request]];
SignInController* SignInController = SignInBridge->GetSignInController();
Controller.delegate = SignInController;
Controller.presentationContextProvider = SignInController;
[Controller performRequests];
bStarted = true;
}
}
else
{
ErrorStr = TEXT("ShowLoginUI: Missing identity interface");
}
}
else
{
ErrorStr = FString::Printf(TEXT("ShowLoginUI: Invalid controller index (%d)"), ControllerIndex);
}
#else
ErrorStr = TEXT("Sign in with Apple is not available");
#endif
if (!bStarted)
{
UE_LOG_ONLINE_EXTERNALUI(Warning, TEXT("%s"), *ErrorStr);
FOnlineError Error(false);
Error.SetFromErrorCode(MoveTemp(ErrorStr));
Subsystem->ExecuteNextTick([ControllerIndex, Delegate, Error = MoveTemp(Error)]()
{
Delegate.ExecuteIfBound(nullptr, ControllerIndex, Error);
});
}
return bStarted;
}
bool FOnlineExternalUIApple::ShowFriendsUI(int32 LocalUserNum)
{
return false;
}
bool FOnlineExternalUIApple::ShowInviteUI(int32 LocalUserNum, FName SessionName)
{
return false;
}
bool FOnlineExternalUIApple::ShowAchievementsUI(int32 LocalUserNum)
{
return false;
}
bool FOnlineExternalUIApple::ShowLeaderboardUI(const FString& LeaderboardName)
{
return false;
}
bool FOnlineExternalUIApple::ShowWebURL(const FString& Url, const FShowWebUrlParams& ShowParams, const FOnShowWebUrlClosedDelegate& Delegate)
{
FPlatformProcess::LaunchURL(*Url, nullptr, nullptr);
return true;
}
bool FOnlineExternalUIApple::CloseWebURL()
{
return false;
}
bool FOnlineExternalUIApple::ShowProfileUI(const FUniqueNetId& Requestor, const FUniqueNetId& Requestee, const FOnProfileUIClosedDelegate& Delegate)
{
return false;
}
bool FOnlineExternalUIApple::ShowAccountUpgradeUI(const FUniqueNetId& UniqueId)
{
return false;
}
bool FOnlineExternalUIApple::ShowStoreUI(int32 LocalUserNum, const FShowStoreParams& ShowParams, const FOnShowStoreUIClosedDelegate& Delegate)
{
return false;
}
bool FOnlineExternalUIApple::ShowSendMessageUI(int32 LocalUserNum, const FShowSendMessageParams& ShowParams, const FOnShowSendMessageUIClosedDelegate& Delegate)
{
return false;
}
void FOnlineExternalUIApple::OnSignInComplete(NSString* user, NSData* authorizationCode, NSString* email, NSPersonNameComponents* fullName, NSError* error, int32 LocalUserNum)
{
SignInBridge->ClearOnSignInCompleteDelegate_Handle(OnSignInCompleteHandle);
bool bWasSuccessful = (user != nullptr);
FString ErrorStr;
if (bWasSuccessful)
{
const FString PlayerId(user);
NSString* authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding];
TSharedRef<FUserOnlineAccountApple> User = MakeShared<FUserOnlineAccountApple>(PlayerId, FString(authorizationCodeStr));
if (fullName)
{
NSString* fullNameStr = [NSPersonNameComponentsFormatter localizedStringFromPersonNameComponents:fullName style:NSPersonNameComponentsFormatterStyleDefault options:(NSPersonNameComponentsFormatterOptions)0];
if ([fullNameStr length] > 0)
{
User->RealName = FString(fullNameStr);
if ([fullName.givenName length] > 0)
{
User->UserAttributes.Emplace(APPLE_USER_ATTR_FIRSTNAME, FString(fullName.givenName));
}
if ([fullName.familyName length] > 0)
{
User->UserAttributes.Emplace(APPLE_USER_ATTR_LASTNAME, FString(fullName.familyName));
}
}
}
if ([email length] > 0)
{
User->UserAttributes.Emplace(APPLE_USER_ATTR_EMAIL, FString(email));
}
FOnlineIdentityApplePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityApple>(Subsystem->GetIdentityInterface());
if (IdentityInterface.IsValid())
{
IdentityInterface->AddCachedAccount(LocalUserNum, User);
Subsystem->ExecuteNextTick([this, User, LocalUserNum]()
{
LoginUIClosedDelegate.ExecuteIfBound(User->GetUserId(), LocalUserNum, FOnlineError::Success());
});
}
}
else
{
// Handle error codes for known error types
switch (error.code)
{
#if SIWA_SUPPORTED
case ASAuthorizationErrorCanceled:
// The user canceled the authorization attempt.
ErrorStr = LOGIN_CANCELLED;
break;
case ASAuthorizationErrorFailed:
// The authorization attempt failed.
ErrorStr = LOGIN_ERROR_AUTH_FAILURE;
break;
case ASAuthorizationErrorInvalidResponse:
// The authorization request received an invalid response.
case ASAuthorizationErrorNotHandled:
// The authorization request wasnt handled.
case ASAuthorizationErrorUnknown:
// The authorization attempt failed for an unknown reason.
// Typical failure code for an invalid application setup
#endif
default:
{
NSString* errstr = [error localizedDescription];
ErrorStr = FString::Printf(TEXT("Login failure %s"), *FString(errstr));
}
break;
}
FOnlineError Error(bWasSuccessful);
Error.SetFromErrorCode(MoveTemp(ErrorStr));
Subsystem->ExecuteNextTick([this, LocalUserNum, Error = MoveTemp(Error)]()
{
LoginUIClosedDelegate.ExecuteIfBound(nullptr, LocalUserNum, Error);
});
}
}