Lesson 35 - Get Compute Auth Token Working

This commit is contained in:
Norman Lansing
2026-02-28 12:32:28 -05:00
parent 1d477ee42a
commit 4fde462bce
7743 changed files with 1397833 additions and 18 deletions

View File

@@ -0,0 +1,233 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "UI/TestAnywhereMenuWidget.h"
#if WITH_GAMELIFT_CLIENT
#include "GameLiftClientSDK.h"
#include "GameLiftClientSDKModels.h"
#include <Kismet/GameplayStatics.h>
#endif
UTestAnywhereMenuWidget::UTestAnywhereMenuWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
#if WITH_GAMELIFT_CLIENT
GameLiftSdkModule = &FModuleManager::LoadModuleChecked<FGameLiftClientSDKModule>(FName("GameLiftClientSDK"));
check(GameLiftSdkModule);
TryFetchCommandLineArguments();
if (!InputCredentialsName.IsEmpty())
{
GameLiftSdkModule->ConfigureClient(InputCredentialsName);
}
#endif
}
void UTestAnywhereMenuWidget::TryFetchCommandLineArguments()
{
FString ArgumentFleetId;
if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereClientFleetId="), ArgumentFleetId))
{
InputFleetId = ArgumentFleetId;
}
FString ArgumentCredentialsName;
if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereClientCredentialsName="), ArgumentCredentialsName))
{
InputCredentialsName = ArgumentCredentialsName;
}
FString ArgumentCustomLocation;
if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereClientCustomLocation="), ArgumentCustomLocation))
{
InputCustomLocation = ArgumentCustomLocation;
}
bool bArgumentFetched = !ArgumentCredentialsName.IsEmpty() && !ArgumentFleetId.IsEmpty() && !ArgumentCustomLocation.IsEmpty();
if (bArgumentFetched)
{
SetOutputMessage(FString::Printf(TEXT("Successfully loaded launch arguments!\n\nCredentials: %s\nFleet ID: %s\nLocation: %s"),
*ArgumentCredentialsName, *ArgumentFleetId, *ArgumentCustomLocation));
}
else
{
FString EmptyString(TEXT("<empty>"));
SetOutputMessage(FString::Printf(TEXT("Failed to load launch arguments!\n\nCredentials: %s\nFleet ID: %s\nLocation: %s"),
ArgumentCredentialsName.IsEmpty() ? *EmptyString : *ArgumentCredentialsName,
ArgumentFleetId.IsEmpty() ? *EmptyString : *ArgumentFleetId,
ArgumentCustomLocation.IsEmpty() ? *EmptyString : *ArgumentCustomLocation), true);
}
}
bool UTestAnywhereMenuWidget::ConnectToServer()
{
#if WITH_GAMELIFT_CLIENT
// Talk to Amazon GameLift to retrieve information to join your game server running on your Anywhere fleet.
if (!ConnectToAmazonGameLift())
{
// If the client cannot create a player session, it will not connect to the running game server.
return false;
}
const FString Options = "?PlayerSessionId=" + ServerPlayerSessionId + "?PlayerId=" + ServerPlayerId;
// This will connect to the server map.
FString LevelName = ServerIpAddress + ":" + ServerPort;
UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName), false, Options);
FString DisplayMessage = FString::Printf(TEXT("Successfully connected to a game session and created a player session!\n\n"));
DisplayMessage += FString::Printf(TEXT("Game Session ID: %s\n"), *ServerGameSessionId);
DisplayMessage += FString::Printf(TEXT("Player Session ID: %s\n"), *ServerPlayerSessionId);
DisplayMessage += FString::Printf(TEXT("Player ID: %s\n"), *ServerPlayerId);
DisplayMessage += FString::Printf(TEXT("Host: %s:%s\n\n"), *ServerIpAddress, *ServerPort);
DisplayMessage += FString::Printf(TEXT("Opening level: %s (Options: %s)"), *LevelName, *Options);
SetOutputMessage(DisplayMessage);
return true;
#else
return false;
#endif
}
bool UTestAnywhereMenuWidget::ConnectToAmazonGameLift()
{
// Find or create a new game session on your Anywhere fleet.
if (!FindGameSession())
{
if (!CreateGameSession(kMaximumPlayerSessionCount))
{
return false;
}
else
{
// A new game session will be in ACTIVATING state for a while. Wait 1 second to connect.
FPlatformProcess::Sleep(1);
}
}
// Create a new player session before joining the game server.
if (!CreatePlayerSession())
{
return false;
}
return true;
}
bool UTestAnywhereMenuWidget::FindGameSession()
{
#if WITH_GAMELIFT_CLIENT
check(GameLiftSdkModule);
if (!ServerGameSessionId.IsEmpty())
{
SetOutputMessage(FString::Printf(TEXT("Game session already started: %s"), *ServerGameSessionId));
return true;
}
FString ErrorMessage;
FString StatusFilter = TEXT("ACTIVE");
const TArray<FGL_GameSession>& GLGameSessionList = GameLiftSdkModule->DescribeGameSessions(InputFleetId, StatusFilter, ErrorMessage);
if (!ErrorMessage.IsEmpty())
{
SetOutputMessage(ErrorMessage, true);
return false;
}
for (const FGL_GameSession& GLGameSession: GLGameSessionList)
{
SetOutputMessage(FString::Printf(TEXT("Found game session: %s"), *(GLGameSession.ToString())));
// Use this game session if it is available.
if (GLGameSession.CurrentPlayerSessionCount < GLGameSession.MaximumPlayerSessionCount)
{
ServerGameSessionId = GLGameSession.GameSessionId;
ServerIpAddress = GLGameSession.IpAddress;
ServerPort = FString::FromInt(GLGameSession.Port);
return true;
}
}
#endif
return false;
}
bool UTestAnywhereMenuWidget::CreateGameSession(size_t InMaxPlayerCount)
{
#if WITH_GAMELIFT_CLIENT
check(GameLiftSdkModule);
if (!ServerGameSessionId.IsEmpty())
{
SetOutputMessage(FString::Printf(TEXT("Game session already started: %s"), *ServerGameSessionId));
return true;
}
FString ErrorMessage;
FGL_GameSession GLGameSession = GameLiftSdkModule->CreateGameSession(InMaxPlayerCount, InputFleetId, ErrorMessage, InputCustomLocation);
if (!ErrorMessage.IsEmpty())
{
SetOutputMessage(ErrorMessage, true);
return false;
}
SetOutputMessage(FString::Printf(TEXT("Successfully created new game session: %s"), *(GLGameSession.ToString())));
ServerGameSessionId = GLGameSession.GameSessionId;
ServerIpAddress = GLGameSession.IpAddress;
ServerPort = FString::FromInt(GLGameSession.Port);
return true;
#else
return false;
#endif
}
bool UTestAnywhereMenuWidget::CreatePlayerSession()
{
#if WITH_GAMELIFT_CLIENT
check(GameLiftSdkModule);
if (ServerGameSessionId.IsEmpty())
{
SetOutputMessage("Game session is empty!", true);
return false;
}
FString ErorrMessage;
const FString PlayerData = TEXT("TestAnywhere_PlayerData");
ServerPlayerId = TEXT("TestAnywhere_Player_") + FString::FromInt(FMath::RandRange(0, kMaximumPlayerSessionCount * 100));
FGL_PlayerSession PlayerSession = GameLiftSdkModule->CreatePlayerSession(ServerGameSessionId, PlayerData, ServerPlayerId, ErorrMessage);
if (!ErorrMessage.IsEmpty())
{
SetOutputMessage(ErorrMessage, true);
return false;
}
SetOutputMessage(FString::Printf(TEXT("Successfully created player session: %s"), *(PlayerSession.ToString())));
ServerPlayerSessionId = PlayerSession.PlayerSessionId;
return true;
#else
return false;
#endif
}
void UTestAnywhereMenuWidget::SetOutputMessage(const FString& InMessage, bool bIsError)
{
#if WITH_GAMELIFT_CLIENT
LogOutputText = FText::FromString(!bIsError ? InMessage : FString("[Error]\n" + InMessage));
LogOutputColor = !bIsError ? FLinearColor::White : FLinearColor::Red;
#endif
}

View File

@@ -0,0 +1,397 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "UI/TestCloudDeploymentMenuWidget.h"
#include "WebBrowser.h"
#include "WebBrowserModule.h"
#include "IWebBrowserSingleton.h"
#include "IWebBrowserCookieManager.h"
#include "Json.h"
#include "JsonUtilities.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Actor.h"
#if WITH_GAMELIFT_CLIENT
#include "aws/gamelift/authentication/exports.h"
#include "aws/gamelift/core/errors.h"
#endif
DEFINE_LOG_CATEGORY(TestCloudDeployment);
static FString sGlobalLoggerError;
UTestCloudDeploymentMenuWidget::UTestCloudDeploymentMenuWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Http = &FHttpModule::Get();
ApiGatewayEndpoint = FString::Printf(TEXT("https://%s.execute-api.%s.amazonaws.com/%s"), TEXT("apigatewayid"), TEXT("region"), TEXT("stage"));
GetGameConnectionURI = TEXT("/get_game_connection");
GetGameConnectionRetryDelayMs = 3000; // wait for 3 sec before the next attempt to get game connection info when matchmaking is in porgress
MatchmakingTimeoutInSecondsParameter = 60; // must match to CFN parameter with the same name
MatchmakingIsTimedOut = false;
StartSessionURI = TEXT("/start_game");
SignupUrl = TEXT("");
CallbackUrl = TEXT("https://aws.amazon.com/");
ConfigFilePath = TEXT("CloudFormation/awsGameLiftClientConfig.yml");
MatchmakingInProgress = false;
}
void UTestCloudDeploymentMenuWidget::NativeConstruct()
{
Super::NativeConstruct();
}
bool UTestCloudDeploymentMenuWidget::OnLoginClicked()
{
if (!MatchmakingInProgress)
{
int result = AuthAndGetToken();
if (result != 0)
{
LatestError = TEXT("Authorization failed");
UE_LOG(TestCloudDeployment, Error, TEXT("%s for user '%s'"), *LatestError, *Username);
return false;
}
EstablishGameConnection();
}
return true;
}
int UTestCloudDeploymentMenuWidget::OnSignupClicked()
{
#if WITH_GAMELIFT_CLIENT
auto SessionLogger = [](unsigned int InLevel, const char* InMessage, int InSize)
{
if (InLevel == GameLift::Logger::Level::Error)
{
sGlobalLoggerError = InMessage;
}
UE_LOG(TestCloudDeployment, Log, TEXT("Session manager logger: %s"), UTF8_TO_TCHAR(InMessage));
};
auto configFileFullPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectContentDir(), ConfigFilePath));
auto sessionManagerHandle = GameLiftSessionManagerInstanceCreate(StringCast<ANSICHAR>(*configFileFullPath).Get(), SessionLogger);
auto cb = [](DISPATCH_RECEIVER_HANDLE dispatchReceiver, const char* charPtr)
{
*(static_cast<FString*>(dispatchReceiver)) = charPtr;
};
const std::string& _Username = StringCast<ANSICHAR>(*Username).Get();
const std::string& _Password = StringCast<ANSICHAR>(*Password).Get();
auto result = GameLiftSignUpByUsernamePassword(sessionManagerHandle, _Username.c_str(), _Password.c_str());
if (result != 0)
{
LatestError = TEXT("SignUp failed");
UE_LOG(TestCloudDeployment, Error, TEXT("%s for user '%s'"), *LatestError, *Username);
}
GameLiftSessionManagerInstanceRelease(sessionManagerHandle);
return result;
#else
return 0;
#endif
}
int UTestCloudDeploymentMenuWidget::OnConfirmSignupClicked()
{
#if WITH_GAMELIFT_CLIENT
auto SessionLogger = [](unsigned int InLevel, const char* InMessage, int InSize)
{
if (InLevel == GameLift::Logger::Level::Error)
{
sGlobalLoggerError = InMessage;
}
UE_LOG(TestCloudDeployment, Log, TEXT("Session manager logger: %s"), UTF8_TO_TCHAR(InMessage));
};
auto ConfigFileFullPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectContentDir(), ConfigFilePath));
if (!FPaths::FileExists(ConfigFileFullPath))
{
LatestError = FString::Printf(TEXT("A config file is not found, make sure to setup it at: %s"), *ConfigFileFullPath);
return GameLift::GAMELIFT_ERROR_GENERAL;
}
auto sessionManagerHandle = GameLiftSessionManagerInstanceCreate(StringCast<ANSICHAR>(*ConfigFileFullPath).Get(), SessionLogger);
auto cb = [](DISPATCH_RECEIVER_HANDLE dispatchReceiver, const char* charPtr)
{
*(static_cast<FString*>(dispatchReceiver)) = charPtr;
};
const std::string& _Username = StringCast<ANSICHAR>(*Username).Get();
const std::string& _VerificationCode = StringCast<ANSICHAR>(*VerificationCode).Get();
auto result = GameLiftConfirmSignUpByUsernameCode(sessionManagerHandle, _Username.c_str(), _VerificationCode.c_str());
if (result != 0)
{
LatestError = TEXT("Confirm SignUp failed");
UE_LOG(TestCloudDeployment, Error, TEXT("%s for user '%s'"), *LatestError, *Username);
}
GameLiftSessionManagerInstanceRelease(sessionManagerHandle);
return result;
#else
return 0;
#endif
}
FString UTestCloudDeploymentMenuWidget::GetLatestError()
{
if (!LatestError.IsEmpty())
{
return FString::Printf(TEXT("Error: %s\n%s"), *LatestError, *sGlobalLoggerError);
}
return FString();
}
int UTestCloudDeploymentMenuWidget::AuthAndGetToken()
{
#if WITH_GAMELIFT_CLIENT
auto sessionLogger = [](unsigned int InLevel, const char* InMessage, int InSize)
{
if (InLevel == GameLift::Logger::Level::Error)
{
sGlobalLoggerError = InMessage;
}
UE_LOG(TestCloudDeployment, Log, TEXT("Session manager logger: %s"), UTF8_TO_TCHAR(InMessage));
};
auto ConfigFileFullPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectContentDir(), ConfigFilePath));
if (!FPaths::FileExists(ConfigFileFullPath))
{
LatestError = FString::Printf(TEXT("A config file is not found, make sure to setup it at: %s"), *ConfigFileFullPath);
return GameLift::GAMELIFT_ERROR_GENERAL;
}
auto SessionManagerHandle = GameLiftSessionManagerInstanceCreate(StringCast<ANSICHAR>(*ConfigFileFullPath).Get(), sessionLogger);
auto CallbackFunc = [](DISPATCH_RECEIVER_HANDLE InDispatchReceiver, const char* InCharPtr)
{
*(static_cast<FString *>(InDispatchReceiver)) = InCharPtr;
};
const std::string& _Username = StringCast<ANSICHAR>(*Username).Get();
const std::string& _Password = StringCast<ANSICHAR>(*Password).Get();
auto result = GameLiftAuthByUsernamePassword(SessionManagerHandle, _Username.c_str(), _Password.c_str());
if (result == GameLift::GAMELIFT_SUCCESS)
{
GameLiftGetTokenId(SessionManagerHandle, &IdToken, CallbackFunc);
if (GameLiftSessionManagerAreSettingsLoaded(SessionManagerHandle, GameLift::FeatureType::Identity))
{
result = GameLiftGetIdentityApiGatewayURL(SessionManagerHandle, &ApiGatewayEndpoint, CallbackFunc);
}
}
GameLiftSessionManagerInstanceRelease(SessionManagerHandle);
if (result != GameLift::GAMELIFT_SUCCESS)
{
return result;
}
if (ApiGatewayEndpoint.IsEmpty() || IdToken.IsEmpty())
{
return GameLift::GAMELIFT_ERROR_NO_ID_TOKEN;
}
return result;
#else
return 0;
#endif
}
bool UTestCloudDeploymentMenuWidget::OpenLevel(const FString& IpAddress, const FString& Port, const FString& Options)
{
#if WITH_GAMELIFT_CLIENT
UE_LOG(TestCloudDeployment, Log, TEXT("OpenLevel: IpAddress '%s', Port '%s', Options '%s'"), *(IpAddress), *(Port), *(Options));
if (!IpAddress.IsEmpty() && !Port.IsEmpty())
{
FString LevelName = IpAddress + ":" + Port;
UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName), false, Options);
return true;
}
return false;
#else
return true;
#endif
}
void UTestCloudDeploymentMenuWidget::EstablishGameConnection()
{
#if WITH_GAMELIFT_CLIENT
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> GameConnectionRequest = Http->CreateRequest();
GameConnectionRequest->SetVerb("POST");
GameConnectionRequest->SetURL(ApiGatewayEndpoint + GetGameConnectionURI);
GameConnectionRequest->SetHeader("Content-Type", "application/json");
GameConnectionRequest->SetHeader("Auth", IdToken);
GameConnectionRequest->OnProcessRequestComplete().BindUObject(this, &UTestCloudDeploymentMenuWidget::OnGetGameConnectionResponse);
GameConnectionRequest->ProcessRequest();
#endif
}
void UTestCloudDeploymentMenuWidget::OnGetGameConnectionResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
#if WITH_GAMELIFT_CLIENT
if (bWasSuccessful)
{
auto& WorldTimerManager = GetWorld()->GetTimerManager();
auto PollReset = [this, &WorldTimerManager]()
{
WorldTimerManager.ClearTimer(PollGameConnectionHandle);
WorldTimerManager.ClearTimer(PollGameConnectionEndHandle);
MatchmakingInProgress = false;
MatchmakingIsTimedOut = false;
};
auto ResponseCode = static_cast<ServerHttpStatusCode>(Response->GetResponseCode());
if (ResponseCode == ServerHttpStatusCode::GetGameConnection_Ready)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
FString IpAddress = JsonObject->GetStringField("IpAddress");
FString Port = JsonObject->GetStringField("Port");
const FString& PlayerSessionId = JsonObject->GetStringField("PlayerSessionId");
const FString& PlayerId = JsonObject->GetStringField("PlayerId");
const FString& Options = "?PlayerSessionId=" + PlayerSessionId + "?PlayerId=" + PlayerId;
UE_LOG(TestCloudDeployment, Log, TEXT("Game connection: GameSessionArn '%s', PlayerSessionId '%s', PlayerId '%s'"), *(JsonObject->GetStringField("GameSessionArn")), *PlayerSessionId, *PlayerId);
if (MatchmakingInProgress)
{
PollReset();
}
OpenLevel(IpAddress, Port, Options);
}
}
else if (ResponseCode == ServerHttpStatusCode::GetGameConnection_NotFound)
{
UE_LOG(TestCloudDeployment, Log, TEXT("No game is found, starting new game..."));
StartGame(IdToken);
}
else if (ResponseCode == ServerHttpStatusCode::GetGameConnection_MatchmakingInProgress)
{
UE_LOG(TestCloudDeployment, Log, TEXT("Wating for the other players to join the game..."));
if (!MatchmakingInProgress)
{
MatchmakingInProgress = true;
MatchmakingIsTimedOut = false;
WorldTimerManager.SetTimer(PollGameConnectionHandle, this, &UTestCloudDeploymentMenuWidget::PollGameConnection, GetGameConnectionRetryDelayMs / 1000.0f, true);
WorldTimerManager.SetTimer(PollGameConnectionEndHandle, this, &UTestCloudDeploymentMenuWidget::PollGameConnectionEnd, (float)MatchmakingTimeoutInSecondsParameter, false);
}
else
{
if (MatchmakingIsTimedOut)
{
PollReset();
LatestError = TEXT("Game connection is timed out. Try to connect again later");
UE_LOG(TestCloudDeployment, Error, TEXT("%s"), *LatestError);
}
}
}
else if (ResponseCode == ServerHttpStatusCode::GetGameConnection_NoServerError)
{
LatestError = TEXT("Server is not deployed, please use another deployment scenario");
UE_LOG(TestCloudDeployment, Error, TEXT("%s. Error code: %d"), *LatestError, ResponseCode);
}
else
{
LatestError = TEXT("Failed to get game connection");
UE_LOG(TestCloudDeployment, Error, TEXT("%s. Error code: %d"), *LatestError, ResponseCode);
if (MatchmakingInProgress)
{
PollReset();
}
}
}
#endif
}
void UTestCloudDeploymentMenuWidget::PollGameConnection()
{
#if WITH_GAMELIFT_CLIENT
EstablishGameConnection();
#endif
}
void UTestCloudDeploymentMenuWidget::PollGameConnectionEnd()
{
#if WITH_GAMELIFT_CLIENT
MatchmakingIsTimedOut = true;
UE_LOG(TestCloudDeployment, Log, TEXT("Game connection timed out is reached"));
#endif
}
void UTestCloudDeploymentMenuWidget::StartGame(FString InIdToken)
{
#if WITH_GAMELIFT_CLIENT
FString UsedToken = ( IdToken.IsEmpty() ? InIdToken : IdToken );
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> StartSessionHttpRequest = Http->CreateRequest();
StartSessionHttpRequest->SetVerb("POST");
StartSessionHttpRequest->SetURL(ApiGatewayEndpoint + StartSessionURI);
StartSessionHttpRequest->SetHeader("Content-Type", "application/json");
StartSessionHttpRequest->SetHeader("Auth", UsedToken);
StartSessionHttpRequest->OnProcessRequestComplete().BindUObject(this, &UTestCloudDeploymentMenuWidget::OnStartGameResponse);
StartSessionHttpRequest->ProcessRequest();
#endif
}
void UTestCloudDeploymentMenuWidget::OnStartGameResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
#if WITH_GAMELIFT_CLIENT
if (bWasSuccessful)
{
auto responseCode = static_cast<ServerHttpStatusCode>(Response->GetResponseCode());
if (responseCode == ServerHttpStatusCode::StartGame_Accepted)
{
UE_LOG(TestCloudDeployment, Log, TEXT("Game was started successfully, establishing game connection..."));
EstablishGameConnection();
}
else if (responseCode == ServerHttpStatusCode::StartGame_Conflict)
{
LatestError = TEXT("Another game request is being processed now");
UE_LOG(TestCloudDeployment, Error, TEXT("%s"), *LatestError);
}
else
{
LatestError = TEXT("Starting game request failed");
UE_LOG(TestCloudDeployment, Error, TEXT("%s with code '%d'"), *LatestError, responseCode);
}
}
#endif
}