From eb4b1631cf1fa2d14f361b480b2678b923c7a018 Mon Sep 17 00:00:00 2001 From: Patryk Fijalkowski Date: Sat, 14 Oct 2023 12:19:59 +0200 Subject: [PATCH] Initial commit with v2.0.0 release --- .gitignore | 2 + GameSwiftSdk.uplugin | 29 +++ README.md | 160 +++++++++++++++ Resources/Icon128.png | Bin 0 -> 7549 bytes Source/GameSwiftSdk/GameSwiftSdk.Build.cs | 23 +++ .../GameSwiftSdk/Private/BackendResponses.cpp | 100 +++++++++ .../GameSwiftAuthenticationManager.cpp | 191 ++++++++++++++++++ Source/GameSwiftSdk/Private/GameSwiftSdk.cpp | 44 ++++ .../Private/GameSwiftWalletManager.cpp | 75 +++++++ .../Private/MultipleLoginsBlocker.cpp | 28 +++ .../GameSwiftSdk/Private/RequestHandler.cpp | 117 +++++++++++ Source/GameSwiftSdk/Private/Utilities.cpp | 94 +++++++++ Source/GameSwiftSdk/Public/BackendResponses.h | 170 ++++++++++++++++ .../Public/GameSwiftAuthenticationManager.h | 52 +++++ Source/GameSwiftSdk/Public/GameSwiftSdk.h | 9 + .../Public/GameSwiftSdkSettings.h | 27 +++ .../Public/GameSwiftWalletManager.h | 27 +++ .../Public/MultipleLoginsBlocker.h | 12 ++ Source/GameSwiftSdk/Public/RequestHandler.h | 27 +++ Source/GameSwiftSdk/Public/Utilities.h | 20 ++ 20 files changed, 1207 insertions(+) create mode 100644 .gitignore create mode 100644 GameSwiftSdk.uplugin create mode 100644 README.md create mode 100644 Resources/Icon128.png create mode 100644 Source/GameSwiftSdk/GameSwiftSdk.Build.cs create mode 100644 Source/GameSwiftSdk/Private/BackendResponses.cpp create mode 100644 Source/GameSwiftSdk/Private/GameSwiftAuthenticationManager.cpp create mode 100644 Source/GameSwiftSdk/Private/GameSwiftSdk.cpp create mode 100644 Source/GameSwiftSdk/Private/GameSwiftWalletManager.cpp create mode 100644 Source/GameSwiftSdk/Private/MultipleLoginsBlocker.cpp create mode 100644 Source/GameSwiftSdk/Private/RequestHandler.cpp create mode 100644 Source/GameSwiftSdk/Private/Utilities.cpp create mode 100644 Source/GameSwiftSdk/Public/BackendResponses.h create mode 100644 Source/GameSwiftSdk/Public/GameSwiftAuthenticationManager.h create mode 100644 Source/GameSwiftSdk/Public/GameSwiftSdk.h create mode 100644 Source/GameSwiftSdk/Public/GameSwiftSdkSettings.h create mode 100644 Source/GameSwiftSdk/Public/GameSwiftWalletManager.h create mode 100644 Source/GameSwiftSdk/Public/MultipleLoginsBlocker.h create mode 100644 Source/GameSwiftSdk/Public/RequestHandler.h create mode 100644 Source/GameSwiftSdk/Public/Utilities.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf51094 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Binaries/ +Intermediate/ \ No newline at end of file diff --git a/GameSwiftSdk.uplugin b/GameSwiftSdk.uplugin new file mode 100644 index 0000000..9e835d7 --- /dev/null +++ b/GameSwiftSdk.uplugin @@ -0,0 +1,29 @@ +{ + "FileVersion": 3, + "VersionName": "2.0.0", + "Version": 200, + "FriendlyName": "GameSwiftUnrealSdk", + "Description": "GameSwift Unreal SDK is Unity toolset created to integrate with GameSwift ID ecosystem.", + "Category": "Other", + "CreatedBy": "GameSwift", + "CreatedByURL": "https://gameswift.io/", + "DocsURL": "https://docs.gameswift.io/gameswift-products/sdk", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "GameSwiftSdk", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ + "Win64", + "Win32", + "Mac", + "IOS", + "Android" + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..084d19b --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +# Overview + +GameSwift Unreal SDK is a toolset created to to help you seamlessly +integrate [GameSwift ecosystem](https://platform.gameswift.io/) into your Unreal Engine projects. Key features of +GameSwift Unreal SDK are: + +* **Rapid integration** + + Installation and integration process is streamlined and can be completed in under 30 minutes. This means you can focus + more on building your game and less on complex API integration. + +* **Safety** + + Firstly, [GameSwift Launcher](https://launcher.gameswift.io/) is equipped with a robust DRM system that guarantees + players launch your game without altering any game files. + Secondly, [Multiple Logins Blocker](#multiple-logins-blocker) ensures that a user account is logged in from exactly + one device at a time. Both of these features prevent various types of abuses and help you maintain a secure and fair + gaming environment. + +* **Blueprint support** + + All SDK features can be accessed both in C++ code and in the Unreal Engine Blueprint system. This versatility + ensures that not only programmers but also non-coding individuals can work on GameSwift ecosystem integration. + +* **Multiplatform support** + + The wrapper is designed to work seamlessly on multiple platforms, making it adaptable for a variety of game + development projects. You can bring the best gaming experiences with GameSwift Unreal SDK for Windows, MacOS and + mobile. + +GameSwift Unreal SDK is compatible with Unreal Engine versions 4.24 and beyond. We are committed to keeping +it up-to-date with newer Unreal Engine releases, including Unreal Engine 5 and upcoming versions. + +# Installation + +1. Add SDK to you project. This can be done in two ways: + * Add SDK as [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to your repository by invoking the + following command: + ```bash + git submodule add https://github.com/GameSwift/gameswift-unreal-sdk YourMainProjectDir/Plugins/GameSwiftUnrealSdk + ``` + * Download this repository contents + from [newest release](https://github.com/GameSwift/gameswift-unreal-sdk/releases/) and unpack the plugin to + the `Plugins/GameSwiftUnrealSdk` folder. If you don't have a `Plugins` folder in your main project directory yet, + create it. +2. Open Project Settings in the editor and navigate to `Plugins > GameSwiftSdk`. Fill in following + fields: `ClientId`, `ClientRedirectUri`, `ClientAuthenticationSecret`. These secrets + are [distributed by GameSwift](#contact-us). + + ![Plugin secrets](https://github-production-user-asset-6210df.s3.amazonaws.com/109578061/275326695-cad47683-f07a-4d60-a9eb-338e454e6f12.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20231015%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231015T155030Z&X-Amz-Expires=300&X-Amz-Signature=5f594705cef1e88f03b416a8397762fcc610522672ffb78ce0e0bed12208ef51&X-Amz-SignedHeaders=host&actor_id=109578061&key_id=0&repo_id=704859033) +3. Add `GameSwiftSdk` to `PublicDependencyModuleNames` in your main `*.Build.cs` file. + +# Integration + +You can handle GameSwift login in 2 ways: [with launcher](#logging-in-from-launcher) +or [without launcher](#logging-in-without-launcher). You can download GameSwift +launcher [here](https://launcher.gameswift.io/). As long as your game targets Windows or MacOS, we strongly recommend to +use data passed from our launcher. By doing so, you won't need to implement any login screen for your game as launcher +already handles user credentials ina secure way.If you are building for mobile, you will need to create a login screen +and implement connection with GameSwift backend [the other way](#logging-in-without-launcher). + +No matter which approach you choose (C++ or Blueprints), be sure that `AuthorizeAndReadUserInfoFromLauncher` +or `LoginAndAuthorize` is called at the application startup.This method wil succeed provided that: + +1. User has internet connection. +2. User did not attempt to login simultaneously from multiple devices (applicable only for + applications with [Multiple Logins Blocker](#multiple-logins-blocker) enabled). +3. User launched the game via launcher instead of your game executable file (provided that you + used [with launcher](#logging-in-from-launcher) flow). + +Otherwise, you should retry calling this method or close the application as it may not be launched the intended way. + +## Launcher authentication - C++ + +Authorizing the user and getting his info can be done by +calling `UGameSwiftAuthenticationManager::AuthorizeAndReadUserInfoFromLauncher`. + +```cpp +void ASampleCharacter::ReadUserInfoFromLauncher() +{ + FGetUserInfoDelegate SuccessDelegate; + SuccessDelegate.BindUFunction(this, "OnSuccess"); + FBaseSdkFailDelegate FailDelegate; + FailDelegate.BindUFunction(this, "OnError"); + + UGameSwiftAuthenticationManager::AuthorizeAndReadUserInfoFromLauncher(SuccessDelegate, FailDelegate); +} + +void ASampleCharacter::OnSuccess(const FOAuthUserInfoResponse& Response) +{ + // success - you can read user nickname and other data from Response +} + +void ASampleCharacter::OnError(const FBaseSdkFailResponse& Response) +{ + // fail - close the application or retry authorization +} +``` + +## Launcher authentication - Blueprint + +`AuthorizeAndReadUserInfoFromLauncher` node is responsible for authorizing the user, who accessed the game through +GameSwift Launcher. + +![Launcher login with Blueprints](https://github-production-user-asset-6210df.s3.amazonaws.com/109578061/275194063-dfb534d1-a6ea-45de-a892-1b60841a6952.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20231015%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231015T155121Z&X-Amz-Expires=300&X-Amz-Signature=509ef88a05d06069d351a8e471035592768ba0e7475ad8517df11d1ef2ccc674&X-Amz-SignedHeaders=host&actor_id=109578061&key_id=0&repo_id=704859033) + +## Non-launcher authentication - C++ + +With this approach you need to create a custom login widget, which will enable user to input the login data and +authorize. + +Authorizing the user and getting his info can be done by calling `UGameSwiftAuthenticationManager::LoginAndAuthorize`. + +```cpp +void ASampleCharacter::LoginAndAuthorize() +{ + Fstring EmailOrNickname = "bob.smith@example.com"; + Fstring Password = "Password123"; + FGetUserInfoDelegate SuccessDelegate; + SuccessDelegate.BindUFunction(this, "OnSuccess"); + FBaseSdkFailDelegate FailDelegate; + FailDelegate.BindUFunction(this, "OnError"); + + UGameSwiftAuthenticationManager::LoginAndAuthorize(EmailOrNickname, Password, + SuccessDelegate, FailDelegate); +} + +void ASampleCharacter::OnSuccess(const FOAuthUserInfoResponse& Response) +{ + // success - you can read user nickname and other data from Response +} + +void ASampleCharacter::OnError(const FBaseSdkFailResponse& Response) +{ + // fail - close the application or retry authorization +} +``` + +## Non-launcher authentication - Blueprint + +`GetUserCredentials` node in the sample below is your implementation of retrieving login data from your custom login +widget. Then, you can pass retrieved data to `LoginAndAuthorize` node. + +![Non launcher login with Blueprints](https://github-production-user-asset-6210df.s3.amazonaws.com/109578061/275194061-6bd7b194-49c9-4898-bc49-1fd146baa9d9.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20231015%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231015T155137Z&X-Amz-Expires=300&X-Amz-Signature=d5ed67496752a06bf27352d60840374e60342f77f816ffa71b04fd17bf8100d3&X-Amz-SignedHeaders=host&actor_id=109578061&key_id=0&repo_id=704859033) + +## Multiple Logins Blocker + +Multiple Logins Blocker is an optional, powerful feature of GameSwift Unreal SDK that ensures a user's account can +only be logged in on one device at a time. This component's setup is done in Project Settings +under `Plugins > GameSwiftSdk`. + +If enabled, this component works in background of your game, sending periodic heartbeats to GameSwift server at time +intervals specified by you. When server receives these heartbeats, it recognizes that the user is already logged in and +will block any additional login attempts from this user for a configurable duration. If you want to configure the lock +duration, feel free to [contact us](#contact-us). + +# Contact Us + +If you have any questions, concerns, or feedback, please don't hesitate to reach out to GameSwift team. We're here to +assist you and can be contacted via email at [piotr.sobiecki@gameswift.io](mailto:piotr.sobiecki@gameswift.io). diff --git a/Resources/Icon128.png b/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..85330e306d00a32213a00736164705b09fc9d76f GIT binary patch literal 7549 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 literal 0 HcmV?d00001 diff --git a/Source/GameSwiftSdk/GameSwiftSdk.Build.cs b/Source/GameSwiftSdk/GameSwiftSdk.Build.cs new file mode 100644 index 0000000..7eede17 --- /dev/null +++ b/Source/GameSwiftSdk/GameSwiftSdk.Build.cs @@ -0,0 +1,23 @@ +using System.IO; +using UnrealBuildTool; + +public class GameSwiftSdk : ModuleRules +{ + public GameSwiftSdk(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); + + PublicDependencyModuleNames.AddRange(new[] + { + "Core", + "CoreUObject", + "HTTP", + "Json", + "JsonUtilities", + "Engine" + }); + } +} \ No newline at end of file diff --git a/Source/GameSwiftSdk/Private/BackendResponses.cpp b/Source/GameSwiftSdk/Private/BackendResponses.cpp new file mode 100644 index 0000000..3d0b185 --- /dev/null +++ b/Source/GameSwiftSdk/Private/BackendResponses.cpp @@ -0,0 +1,100 @@ +#include "BackendResponses.h" + +bool FOAuthUserInfoResponse::Parse(const FJsonObject& JsonObject, FOAuthUserInfoResponse& ParsedResponse) +{ + bool SuccessfulRead = true; + SuccessfulRead &= JsonObject.TryGetStringField("userId", ParsedResponse.UserId); + SuccessfulRead &= JsonObject.TryGetStringField("nickname", ParsedResponse.Nickname); + SuccessfulRead &= JsonObject.TryGetStringField("avatarUrl", ParsedResponse.AvatarUrl); + SuccessfulRead &= JsonObject.TryGetStringField("country", ParsedResponse.Country); + return SuccessfulRead; +} + +bool FAuthorizeResponse::Parse(const FJsonObject& JsonObject, FAuthorizeResponse& ParsedResponse) +{ + bool SuccessfulRead = true; + SuccessfulRead &= JsonObject.TryGetStringField("code", ParsedResponse.Code); + return SuccessfulRead; +} + +bool FTokenResponse::Parse(const FJsonObject& JsonObject, FTokenResponse& ParsedResponse) +{ + bool SuccessfulRead = true; + SuccessfulRead &= JsonObject.TryGetStringField("access_token", ParsedResponse.AccessToken); + SuccessfulRead &= JsonObject.TryGetStringField("refresh_token", ParsedResponse.RefreshToken); + return SuccessfulRead; +} + +bool FWalletsResponse::Parse(const TArray JsonObject, FWalletsResponse& ParsedResponse) +{ + bool SuccessfulRead = true; + + for (const auto WalletElement : JsonObject) + { + FWallet Wallet = FWallet(); + SuccessfulRead &= WalletElement.TryGetStringField("address", Wallet.Address); + SuccessfulRead &= WalletElement.TryGetStringField("name", Wallet.Name); + SuccessfulRead &= WalletElement.TryGetStringField("walletId", Wallet.WalletId); + + TArray Games; + TArray> GamesArray = WalletElement.GetArrayField("games"); + for (const TSharedPtr& GameValue : GamesArray) + { + FGame Game = FGame(); + TSharedPtr GameObject = GameValue->AsObject(); + Game.GameId = GameObject->GetStringField("gameId"); + Games.Add(Game); + } + Wallet.Games = Games; + + FChain Chain = FChain(); + TSharedPtr ChainObject = WalletElement.GetObjectField("chain"); + Chain.ChainId = ChainObject->GetStringField("chainId"); + Chain.ChainName = ChainObject->GetStringField("chainName"); + Wallet.Chain = Chain; + + ParsedResponse.Wallets.Add(Wallet); + } + + return SuccessfulRead; +} + +bool FWalletBalanceResponse::Parse(const FJsonObject& JsonObject, FWalletBalanceResponse& ParsedResponse) +{ + bool SuccessfulRead = true; + + const TArray>* NftsArray; + SuccessfulRead &= JsonObject.TryGetArrayField("nfts", NftsArray); + for (const TSharedPtr& NftValue : *NftsArray) + { + FNft Nft = FNft(); + TSharedPtr GameObject = NftValue->AsObject(); + Nft.Address = GameObject->GetStringField("address"); + Nft.Balance = GameObject->GetNumberField("balance"); + Nft.Id = GameObject->GetStringField("id"); + ParsedResponse.Nfts.Add(Nft); + } + + const TArray>* TokensArray; + SuccessfulRead &= JsonObject.TryGetArrayField("tokens", TokensArray); + for (const TSharedPtr& TokenValue : *TokensArray) + { + FToken Token = FToken(); + TSharedPtr GameObject = TokenValue->AsObject(); + Token.Address = GameObject->GetStringField("address"); + Token.Balance = GameObject->GetNumberField("balance"); + ParsedResponse.Tokens.Add(Token); + } + + return SuccessfulRead; +} + +bool FLoginResponse::Parse(const FJsonObject& JsonObject, FLoginResponse& ParsedResponse) +{ + bool SuccessfulRead = true; + SuccessfulRead &= JsonObject.TryGetStringField("userId", ParsedResponse.UserId); + SuccessfulRead &= JsonObject.TryGetStringField("email", ParsedResponse.Email); + SuccessfulRead &= JsonObject.TryGetStringField("nickname", ParsedResponse.Nickname); + SuccessfulRead &= JsonObject.TryGetStringField("accessToken", ParsedResponse.AccessToken); + return SuccessfulRead; +} diff --git a/Source/GameSwiftSdk/Private/GameSwiftAuthenticationManager.cpp b/Source/GameSwiftSdk/Private/GameSwiftAuthenticationManager.cpp new file mode 100644 index 0000000..91bda2f --- /dev/null +++ b/Source/GameSwiftSdk/Private/GameSwiftAuthenticationManager.cpp @@ -0,0 +1,191 @@ +#include "GameSwiftAuthenticationManager.h" +#include "BackendResponses.h" +#include "Utilities.h" +#include "HttpModule.h" +#include "RequestHandler.h" + +FString UGameSwiftAuthenticationManager::AuthorizedAccessToken = FString(); + +bool UGameSwiftAuthenticationManager::IsAuthorized() +{ + return AuthorizedAccessToken.IsEmpty() == false; +} + +void UGameSwiftAuthenticationManager::AuthorizeAndReadUserInfoFromLauncher(FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FString CmdAccessToken; + FUtilities::ReadCommandLineArgument("-access_token", CmdAccessToken); + + if (CmdAccessToken.IsEmpty()) + { + FBaseSdkFailResponse Response = FBaseSdkFailResponse(); + Response.ErrorCode = 0; + Response.ErrorMessage = "Cannot read -access_token cmd arg"; + ErrorDelegate.ExecuteIfBound(Response); + } + else + { + Authorize(CmdAccessToken, SuccessDelegate, ErrorDelegate); + } +} + +void UGameSwiftAuthenticationManager::Authorize(FString AccessToken, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + TMap Dictionary = { + {"response_type", "code"}, + {"client_id", FUtilities::GetClientId()}, + {"redirect_uri", FUtilities::GetRedirectUri()}, + {"code_challenge_method", "S256"}, + {"state", "empty"}, + }; + + + FString AuthorizeUrl = FUtilities::GetEncodedQueryString("oauth/authorize?", Dictionary); + + + auto HttpRequest = FRequestHandler::SendRequest(FUtilities::GetGameSwiftIdUri(AuthorizeUrl), "GET", "", + AccessToken); + HttpRequest->OnProcessRequestComplete().BindStatic(&UGameSwiftAuthenticationManager::OnAuthorized, + SuccessDelegate, + ErrorDelegate); + HttpRequest->ProcessRequest(); +} + +void UGameSwiftAuthenticationManager::OnAuthorized(FHttpRequestPtr _, FHttpResponsePtr HttpResponse, + bool bSucceeded, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FJsonObject ResultObject; + FBaseSdkFailResponse errorResult; + + if (FRequestHandler::DecodeRequest(HttpResponse, bSucceeded, ResultObject, errorResult)) + { + FAuthorizeResponse outResult; + if (FAuthorizeResponse::Parse(ResultObject, outResult)) + { + RetrieveOauthToken(outResult.Code, SuccessDelegate, ErrorDelegate); + } + } + else + { + ErrorDelegate.ExecuteIfBound(errorResult); + } +} + +void UGameSwiftAuthenticationManager::RetrieveOauthToken(const FString& Code, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + TMap Dictionary = { + {"client_id", FUtilities::GetClientId()}, + {"grant_type", "authorization_code"}, + {"code", Code}, + {"redirect_uri", FUtilities::GetRedirectUri()}, + }; + + FString QueryString = FUtilities::GetEncodedQueryString("", Dictionary); + + auto HttpRequest = FRequestHandler::SendRequest(FUtilities::GetGameSwiftIdUri("oauth/token"), "POST", + QueryString, "", true); + HttpRequest->OnProcessRequestComplete().BindStatic(&UGameSwiftAuthenticationManager::OnTokenRetrieved, + SuccessDelegate, + ErrorDelegate); + HttpRequest->ProcessRequest(); +} + + +void UGameSwiftAuthenticationManager::OnTokenRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, + bool bSucceeded, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FJsonObject ResultObject; + FBaseSdkFailResponse errorResult; + + if (FRequestHandler::DecodeRequest(HttpResponse, bSucceeded, ResultObject, errorResult)) + { + FTokenResponse outResult; + if (FTokenResponse::Parse(ResultObject, outResult)) + { + AuthorizedAccessToken = outResult.AccessToken; + GetUserInfo(SuccessDelegate, ErrorDelegate); + } + } + else + { + ErrorDelegate.ExecuteIfBound(errorResult); + } +} + + +void UGameSwiftAuthenticationManager::GetUserInfo(FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + auto HttpRequest = FRequestHandler::SendRequest(FUtilities::GetGameSwiftIdUri("oauth/me"), "GET", "", + AuthorizedAccessToken); + HttpRequest->OnProcessRequestComplete().BindStatic( + &UGameSwiftAuthenticationManager::OnOauthUserInformationRetrieved, + SuccessDelegate, ErrorDelegate); + HttpRequest->ProcessRequest(); +} + +void UGameSwiftAuthenticationManager::OnOauthUserInformationRetrieved(FHttpRequestPtr HttpRequest, + FHttpResponsePtr HttpResponse, + bool bSucceeded, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FJsonObject ResultObject; + FBaseSdkFailResponse errorResult; + + if (FRequestHandler::DecodeRequest(HttpResponse, bSucceeded, ResultObject, errorResult)) + { + FOAuthUserInfoResponse outResult; + if (FOAuthUserInfoResponse::Parse(ResultObject, outResult)) + { + SuccessDelegate.ExecuteIfBound(outResult); + } + } + else + { + ErrorDelegate.ExecuteIfBound(errorResult); + } +} + +void UGameSwiftAuthenticationManager::LoginAndAuthorize(FString EmailOrNickname, FString Password, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + TMap Dictionary = { + {"emailOrNickname", EmailOrNickname}, + {"password", Password}, + }; + FString RequestBody = FUtilities::GetJsonString(Dictionary); + FString FullUrl = FUtilities::GetGameSwiftIdUri("auth/login"); + auto HttpRequest = FRequestHandler::SendRequest(FullUrl, "POST", RequestBody, ""); + HttpRequest->OnProcessRequestComplete().BindStatic(&UGameSwiftAuthenticationManager::OnLogin, SuccessDelegate, + ErrorDelegate); + HttpRequest->ProcessRequest(); +} + +void UGameSwiftAuthenticationManager::OnLogin(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, + bool bSucceeded, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FJsonObject ResultObject; + FBaseSdkFailResponse errorResult; + + if (FRequestHandler::DecodeRequest(HttpResponse, bSucceeded, ResultObject, errorResult)) + { + FLoginResponse outResult; + if (FLoginResponse::Parse(ResultObject, outResult)) + { + Authorize(outResult.AccessToken, SuccessDelegate, ErrorDelegate); + } + } + else + { + ErrorDelegate.ExecuteIfBound(errorResult); + } +} diff --git a/Source/GameSwiftSdk/Private/GameSwiftSdk.cpp b/Source/GameSwiftSdk/Private/GameSwiftSdk.cpp new file mode 100644 index 0000000..6ee4551 --- /dev/null +++ b/Source/GameSwiftSdk/Private/GameSwiftSdk.cpp @@ -0,0 +1,44 @@ +#include "GameSwiftSdk.h" + +#include "GameSwiftSdkSettings.h" +#include "MultipleLoginsBlocker.h" +#include "Utilities.h" +#include "Developer/Settings/Public/ISettingsModule.h" +#include "Developer/Settings/Public/ISettingsSection.h" + +#define LOCTEXT_NAMESPACE "FGameSwiftSdkModule" + +void FGameSwiftSdkModule::StartupModule() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + TSharedPtr SettingsSection = SettingsModule->RegisterSettings( + "Project", + "Plugins", + "GameSwiftSdk", + FText::FromString("GameSwiftSdk"), + FText::FromString("UE plugin making it easy to connect with GameSwift ecosystem via GameSwift ID."), + GetMutableDefault()); + } + + if (FUtilities::IsMultipleLoginBlockingEnabled()) + { + float Rate = FUtilities::GetMultipleLoginBlockingRate(); + if (Rate >= 10.0f) + { + FMultipleLoginsBlocker::InitializeBlocking(Rate); + } + } +} + +void FGameSwiftSdkModule::ShutdownModule() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "GameSwiftSdk"); + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FGameSwiftSdkModule, GameSwiftSdk) diff --git a/Source/GameSwiftSdk/Private/GameSwiftWalletManager.cpp b/Source/GameSwiftSdk/Private/GameSwiftWalletManager.cpp new file mode 100644 index 0000000..e553450 --- /dev/null +++ b/Source/GameSwiftSdk/Private/GameSwiftWalletManager.cpp @@ -0,0 +1,75 @@ +#include "GameSwiftWalletManager.h" + +#include "GameSwiftAuthenticationManager.h" +#include "RequestHandler.h" +#include "Utilities.h" + +void UGameSwiftWalletManager::GetWallets(FGetWalletsDelegate SuccessDelegate, FBaseSdkFailDelegate ErrorDelegate) +{ + auto HttpRequest = FRequestHandler::SendRequest(FUtilities::GetGameSwiftIdUri("wallet"), TEXT("GET"), "", + UGameSwiftAuthenticationManager::AuthorizedAccessToken); + HttpRequest->OnProcessRequestComplete().BindStatic(&UGameSwiftWalletManager::OnWalletsRetrieved, SuccessDelegate, + ErrorDelegate); + HttpRequest->ProcessRequest(); +} + +void UGameSwiftWalletManager::OnWalletsRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, + bool bSucceeded, FGetWalletsDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + TArray ResultObject; + FBaseSdkFailResponse errorResult; + + if (FRequestHandler::DecodeRequestArray(HttpResponse, bSucceeded, ResultObject, errorResult)) + { + FWalletsResponse outResult; + if (FWalletsResponse::Parse(ResultObject, outResult) == true) + { + SuccessDelegate.ExecuteIfBound(outResult); + } + else + { + FBaseSdkFailResponse ParseError = FBaseSdkFailResponse(); + ParseError.ErrorMessage = "Cannot parse wallets"; + ErrorDelegate.ExecuteIfBound(ParseError); + } + } + else + { + ErrorDelegate.ExecuteIfBound(errorResult); + } +} + +void UGameSwiftWalletManager::GetWalletBalance(FText WalletId, FGetWalletBalanceDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FString UriSuffix = "wallet/" + WalletId.ToString() + "/balance"; + auto HttpRequest = FRequestHandler::SendRequest(FUtilities::GetGameSwiftIdUri(UriSuffix), TEXT("GET"), "", + UGameSwiftAuthenticationManager::AuthorizedAccessToken); + HttpRequest->OnProcessRequestComplete().BindStatic(&UGameSwiftWalletManager::OnWalletBalanceRetrieved, + SuccessDelegate, + ErrorDelegate); + HttpRequest->ProcessRequest(); +} + + +void UGameSwiftWalletManager::OnWalletBalanceRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, + bool bSucceeded, FGetWalletBalanceDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate) +{ + FJsonObject ResultObject; + FBaseSdkFailResponse errorResult; + + if (FRequestHandler::DecodeRequest(HttpResponse, bSucceeded, ResultObject, errorResult)) + { + FWalletBalanceResponse outResult; + if (FWalletBalanceResponse::Parse(ResultObject, outResult)) + { + SuccessDelegate.ExecuteIfBound(outResult); + } + } + else + { + ErrorDelegate.ExecuteIfBound(errorResult); + } +} diff --git a/Source/GameSwiftSdk/Private/MultipleLoginsBlocker.cpp b/Source/GameSwiftSdk/Private/MultipleLoginsBlocker.cpp new file mode 100644 index 0000000..1a44f1c --- /dev/null +++ b/Source/GameSwiftSdk/Private/MultipleLoginsBlocker.cpp @@ -0,0 +1,28 @@ +#include "MultipleLoginsBlocker.h" + +#include "GameSwiftAuthenticationManager.h" + +FTickerDelegate FMultipleLoginsBlocker::OnHeartbeatDelegate; + +void FMultipleLoginsBlocker::InitializeBlocking(const float HeartbeatRate) +{ + OnHeartbeatDelegate.BindStatic(&FMultipleLoginsBlocker::HeartbeatAction); + +#if ENGINE_MAJOR_VERSION == 5 + FTSTicker:: +#else + FTicker:: +#endif + GetCoreTicker().AddTicker(OnHeartbeatDelegate, HeartbeatRate); +} + +bool FMultipleLoginsBlocker::HeartbeatAction(float DeltaTime) +{ + if (UGameSwiftAuthenticationManager::IsAuthorized()) + { + const FGetUserInfoDelegate SuccessDelegate; + const FBaseSdkFailDelegate FailDelegate; + UGameSwiftAuthenticationManager::GetUserInfo(SuccessDelegate, FailDelegate); + } + return true; +} diff --git a/Source/GameSwiftSdk/Private/RequestHandler.cpp b/Source/GameSwiftSdk/Private/RequestHandler.cpp new file mode 100644 index 0000000..aaa06c2 --- /dev/null +++ b/Source/GameSwiftSdk/Private/RequestHandler.cpp @@ -0,0 +1,117 @@ +#include "RequestHandler.h" +#include "Serialization/JsonSerializer.h" +#include "BackendResponses.h" +#include "Utilities.h" + + +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION <= 24 +TSharedRef FRequestHandler::SendRequest( +#else +TSharedRef FRequestHandler::SendRequest( +#endif + FString FullUrl, FString Method, const FString callBody, const FString accessToken, bool ExpectsWwwResponse) +{ + auto HttpRequest = FHttpModule::Get().CreateRequest(); + HttpRequest->SetVerb(Method); + HttpRequest->SetURL(FullUrl); + FString ContentType = ExpectsWwwResponse ? TEXT("application/x-www-form-urlencoded") : TEXT("application/json"); + HttpRequest->SetHeader(TEXT("Content-Type"), ContentType); + HttpRequest->SetHeader(TEXT("gs-authentication"), FUtilities::GetClientSecret()); + + if (accessToken != TEXT("")) + { + HttpRequest->SetHeader("Authorization", "Bearer " + accessToken); + } + + HttpRequest->SetContentAsString(callBody); + return HttpRequest; +} + +bool FRequestHandler::DecodeRequest(FHttpResponsePtr HttpResponse, bool bSucceeded, FJsonObject& ResultObject, + FBaseSdkFailResponse& OutError) +{ + if (bSucceeded && HttpResponse.IsValid()) + { + FString ResponseStr = HttpResponse->GetContentAsString(); + + if (EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode())) + { + TSharedPtr JsonObject; + TSharedRef> JsonReader = TJsonReaderFactory<>::Create(ResponseStr); + + + if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) + { + ResultObject = *JsonObject; + return true; + } + } + else + { + TSharedPtr JsonObject; + TSharedRef> JsonReader = TJsonReaderFactory<>::Create(ResponseStr); + + if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) + { + OutError = DecodeError(JsonObject); + return false; + } + } + } + + OutError.ErrorCode = 408; + OutError.ErrorName = OutError.ErrorMessage = TEXT("Request Timeout or null response"); + return false; +} + +bool FRequestHandler::DecodeRequestArray(FHttpResponsePtr HttpResponse, bool bSucceeded, + TArray& ResultObject, + FBaseSdkFailResponse& OutError) +{ + if (bSucceeded && HttpResponse.IsValid()) + { + FString ResponseStr = HttpResponse->GetContentAsString(); + + if (EHttpResponseCodes::IsOk(HttpResponse->GetResponseCode())) + { + ResponseStr = "{\"temp\":" + ResponseStr + "}"; + + TSharedPtr JsonObject; + TSharedRef> JsonReader = TJsonReaderFactory<>::Create(ResponseStr); + if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) + { + for (const TSharedPtr ArrayElement : JsonObject->GetArrayField("temp")) + { + ResultObject.Add(*ArrayElement->AsObject()); + } + return true; + } + } + else + { + TSharedPtr JsonObject; + TSharedRef> JsonReader = TJsonReaderFactory<>::Create(ResponseStr); + + if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) + { + OutError = DecodeError(JsonObject); + return false; + } + } + } + + OutError.ErrorCode = 408; + OutError.ErrorName = OutError.ErrorMessage = TEXT("Request Timeout or null response"); + return false; +} + +FBaseSdkFailResponse FRequestHandler::DecodeError(TSharedPtr JsonObject) +{ + FBaseSdkFailResponse OutError = FBaseSdkFailResponse(); + + JsonObject->TryGetNumberField(TEXT("statusCode"), OutError.ErrorCode); + JsonObject->TryGetStringField(TEXT("message"), OutError.ErrorMessage); + JsonObject->TryGetStringField(TEXT("error"), OutError.ErrorName); + + return OutError; +} diff --git a/Source/GameSwiftSdk/Private/Utilities.cpp b/Source/GameSwiftSdk/Private/Utilities.cpp new file mode 100644 index 0000000..0ca13b6 --- /dev/null +++ b/Source/GameSwiftSdk/Private/Utilities.cpp @@ -0,0 +1,94 @@ +#include "Utilities.h" +#include "GenericPlatformHttp.h" + +void FUtilities::ReadCommandLineArgument(const char* ArgumentName, FString& ArgumentValue) +{ + FString CommandLineArgs = FCommandLine::Get(); + + TArray ArgArray; + CommandLineArgs.ParseIntoArray(ArgArray, TEXT(" "), true); + + for (int32 Index = 0; Index < ArgArray.Num() - 1; ++Index) + { + if (ArgArray[Index] == ArgumentName) + { + ArgumentValue = ArgArray[Index + 1]; + break; + } + } +} + +FString FUtilities::GetEncodedQueryString(const FString& UrlBase, const TMap& Parameters) +{ + FString QueryString; + + for (const TPair& Param : Parameters) + { + if (QueryString.IsEmpty() == false) + { + QueryString += TEXT("&"); + } + + FString Key = FGenericPlatformHttp::UrlEncode(Param.Key); + FString Value = FGenericPlatformHttp::UrlEncode(Param.Value); + QueryString += Key + TEXT("=") + Value; + } + + return UrlBase + QueryString; +} + +FString FUtilities::GetClientSecret() +{ + return GetSdkSettings()->ClientAuthenticationSecret; +} + +FString FUtilities::GetClientId() +{ + return GetSdkSettings()->ClientId; + // return TEXT("7ac441a0-537d-4843-ba54-9aab05c7e8bf"); +} + +FString FUtilities::GetRedirectUri() +{ + return GetSdkSettings()->ClientRedirectUri; + // return TEXT("https://id.gameswift.io/api/oauth/callback"); +} + +bool FUtilities::IsMultipleLoginBlockingEnabled() +{ + return GetSdkSettings()->MultipleLoginsBlockerEnabled; +} + +float FUtilities::GetMultipleLoginBlockingRate() +{ + return GetSdkSettings()->MultipleLoginsBlockerHeartbeatRate; +} + +UGameSwiftSdkSettings* FUtilities::GetSdkSettings() +{ + return GetMutableDefault(); +} + +FString FUtilities::GetGameSwiftIdUri(FString EndpointAddress) +{ + return "https://id.gameswift.io/api/1/" + EndpointAddress; +} + +FString FUtilities::GetJsonString(TMap Map) +{ + TSharedPtr JsonObject = MakeShareable(new FJsonObject); + + for (const auto& KeyValue : Map) + { + const FString& Key = KeyValue.Key; + const FString& Value = KeyValue.Value; + + JsonObject->SetStringField(Key, Value); + } + + FString JsonString; + TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&JsonString); + FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); + + return JsonString; +} diff --git a/Source/GameSwiftSdk/Public/BackendResponses.h b/Source/GameSwiftSdk/Public/BackendResponses.h new file mode 100644 index 0000000..5c2a2ef --- /dev/null +++ b/Source/GameSwiftSdk/Public/BackendResponses.h @@ -0,0 +1,170 @@ +#pragma once + +#include "CoreMinimal.h" +#include "BackendResponses.generated.h" + + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FBaseSdkFailResponse +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + int32 ErrorCode; + UPROPERTY(BlueprintReadOnly) + FString ErrorName; + UPROPERTY(BlueprintReadOnly) + FString ErrorMessage; +}; + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FOAuthUserInfoResponse +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString UserId; + UPROPERTY(BlueprintReadOnly) + FString Nickname; + UPROPERTY(BlueprintReadOnly) + FString AvatarUrl; + UPROPERTY(BlueprintReadOnly) + FString Country; + + static bool Parse(const FJsonObject& JsonObject, FOAuthUserInfoResponse& ParsedResponse); +}; + + +struct GAMESWIFTSDK_API FAuthorizeResponse +{ + FString Code; + + static bool Parse(const FJsonObject& JsonObject, FAuthorizeResponse& ParsedResponse); +}; + + +struct GAMESWIFTSDK_API FTokenResponse +{ + FString AccessToken; + FString RefreshToken; + + static bool Parse(const FJsonObject& JsonObject, FTokenResponse& ParsedResponse); +}; + + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FChain +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString ChainId; + + + UPROPERTY(BlueprintReadOnly) + FString ChainName; +}; + + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FGame +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString GameId; +}; + + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FWallet +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString Address; + + UPROPERTY(BlueprintReadOnly) + TArray Games; + + UPROPERTY(BlueprintReadOnly) + FString Name; + + UPROPERTY(BlueprintReadOnly) + FString WalletId; + + UPROPERTY(BlueprintReadOnly) + FChain Chain; +}; + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FWalletsResponse +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + TArray Wallets; + + static bool Parse(const TArray JsonObject, FWalletsResponse& ParsedResponse); +}; + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FNft +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString Address; + + UPROPERTY(BlueprintReadOnly) + float Balance; + + UPROPERTY(BlueprintReadOnly) + FString Id; +}; + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FToken +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString Address; + + UPROPERTY(BlueprintReadOnly) + float Balance; +}; + + +USTRUCT(BlueprintType) +struct GAMESWIFTSDK_API FWalletBalanceResponse +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + TArray Nfts; + + UPROPERTY(BlueprintReadOnly) + TArray Tokens; + + static bool Parse(const FJsonObject& JsonObject, FWalletBalanceResponse& ParsedResponse); +}; + +struct GAMESWIFTSDK_API FLoginResponse +{ + FString UserId; + FString Email; + FString Nickname; + FString AccessToken; + + static bool Parse(const FJsonObject& JsonObject, FLoginResponse& ParsedResponse); +}; + + +DECLARE_DYNAMIC_DELEGATE_OneParam(FBaseSdkFailDelegate, const FBaseSdkFailResponse&, Response); + +DECLARE_DYNAMIC_DELEGATE_OneParam(FGetUserInfoDelegate, const FOAuthUserInfoResponse&, Response); + +DECLARE_DYNAMIC_DELEGATE_OneParam(FGetWalletsDelegate, const FWalletsResponse&, Response); + +DECLARE_DYNAMIC_DELEGATE_OneParam(FGetWalletBalanceDelegate, const FWalletBalanceResponse&, Response); diff --git a/Source/GameSwiftSdk/Public/GameSwiftAuthenticationManager.h b/Source/GameSwiftSdk/Public/GameSwiftAuthenticationManager.h new file mode 100644 index 0000000..3d89e85 --- /dev/null +++ b/Source/GameSwiftSdk/Public/GameSwiftAuthenticationManager.h @@ -0,0 +1,52 @@ +#pragma once +#include "CoreMinimal.h" +#include "Interfaces/IHttpRequest.h" +#include "BackendResponses.h" +#include "GameSwiftAuthenticationManager.generated.h" + + +UCLASS() +class GAMESWIFTSDK_API UGameSwiftAuthenticationManager : public UObject +{ + GENERATED_BODY() + +public: + static bool IsAuthorized(); + static FString AuthorizedAccessToken; + + UFUNCTION(BlueprintCallable, Category = "GameSwiftSdk | Authentication") + static void AuthorizeAndReadUserInfoFromLauncher(FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + + UFUNCTION(BlueprintCallable, Category = "GameSwiftSdk | Authentication") + static void LoginAndAuthorize(FString EmailOrNickname, FString Password, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + + UFUNCTION(BlueprintCallable, Category = "GameSwiftSdk | Authentication") + static void GetUserInfo(FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + +private: + static void Authorize(FString AccessToken, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + static void RetrieveOauthToken(const FString& Code, FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + static void OnAuthorized(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + + static void OnTokenRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + + + static void OnOauthUserInformationRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, + bool bSucceeded, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + + + static void OnLogin(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, + FGetUserInfoDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); +}; diff --git a/Source/GameSwiftSdk/Public/GameSwiftSdk.h b/Source/GameSwiftSdk/Public/GameSwiftSdk.h new file mode 100644 index 0000000..79f4222 --- /dev/null +++ b/Source/GameSwiftSdk/Public/GameSwiftSdk.h @@ -0,0 +1,9 @@ +#pragma once + +#include "CoreMinimal.h" + +class FGameSwiftSdkModule : public IModuleInterface +{ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/GameSwiftSdk/Public/GameSwiftSdkSettings.h b/Source/GameSwiftSdk/Public/GameSwiftSdkSettings.h new file mode 100644 index 0000000..2b9fb67 --- /dev/null +++ b/Source/GameSwiftSdk/Public/GameSwiftSdkSettings.h @@ -0,0 +1,27 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "GameSwiftSdkSettings.generated.h" + +UCLASS(config = Engine, defaultconfig) +class UGameSwiftSdkSettings : public UObject +{ + GENERATED_BODY() + +public: + UPROPERTY(config, EditAnywhere, Category = "GameSwiftSdk") + FString ClientId; + + UPROPERTY(config, EditAnywhere, Category = "GameSwiftSdk") + FString ClientRedirectUri; + + UPROPERTY(config, EditAnywhere, Category = "GameSwiftSdk") + FString ClientAuthenticationSecret; + + UPROPERTY(config, EditAnywhere, Category = "GameSwiftSdk") + bool MultipleLoginsBlockerEnabled; + + UPROPERTY(config, EditAnywhere, Category = "GameSwiftSdk") + float MultipleLoginsBlockerHeartbeatRate; +}; diff --git a/Source/GameSwiftSdk/Public/GameSwiftWalletManager.h b/Source/GameSwiftSdk/Public/GameSwiftWalletManager.h new file mode 100644 index 0000000..feb3e62 --- /dev/null +++ b/Source/GameSwiftSdk/Public/GameSwiftWalletManager.h @@ -0,0 +1,27 @@ +#pragma once +#include "BackendResponses.h" +#include "IHttpRequest.h" +#include "GameSwiftWalletManager.generated.h" + + +UCLASS() +class GAMESWIFTSDK_API UGameSwiftWalletManager : public UObject +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "GameSwiftSdk | Wallet") + static void GetWallets(FGetWalletsDelegate SuccessDelegate, FBaseSdkFailDelegate ErrorDelegate); + + UFUNCTION(BlueprintCallable, Category = "GameSwiftSdk | Wallet") + static void GetWalletBalance(FText WalletId, FGetWalletBalanceDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + +private: + static void OnWalletsRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, + FGetWalletsDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); + static void OnWalletBalanceRetrieved(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, + FGetWalletBalanceDelegate SuccessDelegate, + FBaseSdkFailDelegate ErrorDelegate); +}; diff --git a/Source/GameSwiftSdk/Public/MultipleLoginsBlocker.h b/Source/GameSwiftSdk/Public/MultipleLoginsBlocker.h new file mode 100644 index 0000000..b26b6d8 --- /dev/null +++ b/Source/GameSwiftSdk/Public/MultipleLoginsBlocker.h @@ -0,0 +1,12 @@ +#pragma once +#include "Ticker.h" + +class FMultipleLoginsBlocker +{ +public: + static void InitializeBlocking(float HeartbeatRate); + +private: + static FTickerDelegate OnHeartbeatDelegate; + static bool HeartbeatAction(float DeltaTime); +}; diff --git a/Source/GameSwiftSdk/Public/RequestHandler.h b/Source/GameSwiftSdk/Public/RequestHandler.h new file mode 100644 index 0000000..5782cfc --- /dev/null +++ b/Source/GameSwiftSdk/Public/RequestHandler.h @@ -0,0 +1,27 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Http.h" +#include "JsonObject.h" + +struct FOAuthUserInfoResponse; +struct FBaseSdkFailResponse; +struct FBaseSuccessResponse; + +class FRequestHandler +{ +public: +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION <= 24 + static TSharedRef SendRequest( +#else + static TSharedRef SendRequest( +#endif + FString FullUrl, FString Method, const FString callBody, const FString accessToken, + bool ExpectsWwwResponse = false); + + static bool DecodeRequest(FHttpResponsePtr HttpResponse, bool bSucceeded, FJsonObject& ResultObject, + FBaseSdkFailResponse& OutError); + static bool DecodeRequestArray(FHttpResponsePtr HttpResponse, bool bSucceeded, TArray& ResultObject, + FBaseSdkFailResponse& OutError); + static FBaseSdkFailResponse DecodeError(TSharedPtr JsonObject); +}; diff --git a/Source/GameSwiftSdk/Public/Utilities.h b/Source/GameSwiftSdk/Public/Utilities.h new file mode 100644 index 0000000..4c1a6ac --- /dev/null +++ b/Source/GameSwiftSdk/Public/Utilities.h @@ -0,0 +1,20 @@ +#pragma once +#include "GameSwiftSdkSettings.h" + +class FUtilities +{ +public: + static void ReadCommandLineArgument(const char* ArgumentName, FString& ArgumentValue); + static FString GetEncodedQueryString(const FString& UrlBase, const TMap& Parameters); + + static FString GetClientSecret(); + static FString GetClientId(); + static FString GetRedirectUri(); + static bool IsMultipleLoginBlockingEnabled(); + static float GetMultipleLoginBlockingRate(); + static FString GetGameSwiftIdUri(FString EndpointAddress); + static FString GetJsonString(TMap Map); + +private: + static UGameSwiftSdkSettings* GetSdkSettings(); +};