diff --git a/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset b/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset new file mode 100644 index 0000000..1cf54e3 Binary files /dev/null and b/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset differ diff --git a/Content/Examples/Widgets/WBP_Thirdweb_InApp.uasset b/Content/Examples/Widgets/WBP_Thirdweb_InApp.uasset index 0dc2293..17ba006 100644 Binary files a/Content/Examples/Widgets/WBP_Thirdweb_InApp.uasset and b/Content/Examples/Widgets/WBP_Thirdweb_InApp.uasset differ diff --git a/Content/Level_Thirdweb.umap b/Content/Level_Thirdweb.umap index 797178d..3aab272 100644 Binary files a/Content/Level_Thirdweb.umap and b/Content/Level_Thirdweb.umap differ diff --git a/Source/ThirdParty/Android/libthirdweb.a b/Source/ThirdParty/Android/libthirdweb.a index 65694fe..e3bedf3 100644 --- a/Source/ThirdParty/Android/libthirdweb.a +++ b/Source/ThirdParty/Android/libthirdweb.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5352dbc90c25b87ee111b22e54a6801a5ea70a7f009105e5ff019fd8377a7407 -size 87136750 +oid sha256:1e311209aa3b935090618546f4430113a75864b9455e23b9b9476765c84031a9 +size 87144306 diff --git a/Source/ThirdParty/IOS/libthirdweb.a b/Source/ThirdParty/IOS/libthirdweb.a index 8988a0c..d68a536 100644 --- a/Source/ThirdParty/IOS/libthirdweb.a +++ b/Source/ThirdParty/IOS/libthirdweb.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bee5c3774de1fa9e01c3a26ae9d69974652bf43ca16f9d27c3aeea4122fa0eb0 -size 60436792 +oid sha256:aca4fd5d371de5f1ff3ebd022a6beeb16fb707c875c565fbf63e5abec2c7d280 +size 60426152 diff --git a/Source/ThirdParty/IOS/libthirdweb.sim.a b/Source/ThirdParty/IOS/libthirdweb.sim.a index d91dbec..5abbe35 100644 --- a/Source/ThirdParty/IOS/libthirdweb.sim.a +++ b/Source/ThirdParty/IOS/libthirdweb.sim.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb48d799059dcdd5fab3c74687c3eb8cacf3a92babf8c0f375b261d98c999216 -size 60300952 +oid sha256:f10835df3845840eb8041ac7def2a215fd0057f82c4104e5497062c3614aa9af +size 60292464 diff --git a/Source/ThirdParty/Linux/libthirdweb.a b/Source/ThirdParty/Linux/libthirdweb.a index 944702c..1669b62 100644 --- a/Source/ThirdParty/Linux/libthirdweb.a +++ b/Source/ThirdParty/Linux/libthirdweb.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb423856cb42dcca0da05a0d7345b9b2dd22bcd95dfe090da48b26a7251c7927 -size 92422556 +oid sha256:8c2c127fd260c182a027a33f8e15c2ca71f158338ea56bccf9f150b95174dc2e +size 92429216 diff --git a/Source/ThirdParty/LinuxArm64/libthirdweb.a b/Source/ThirdParty/LinuxArm64/libthirdweb.a index 1e1bd58..6b1a0a1 100644 --- a/Source/ThirdParty/LinuxArm64/libthirdweb.a +++ b/Source/ThirdParty/LinuxArm64/libthirdweb.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83a2d7b221328c40550d9b20c5c08ff3a380885e3716977123288276a4b922df -size 93387736 +oid sha256:9618e0028b7b79a8b60098a338d656f6b8d7b1cddaafff615d3879d101d90c35 +size 93394062 diff --git a/Source/ThirdParty/Mac/libthirdweb.a b/Source/ThirdParty/Mac/libthirdweb.a index 26a82c1..5a9db54 100644 --- a/Source/ThirdParty/Mac/libthirdweb.a +++ b/Source/ThirdParty/Mac/libthirdweb.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67e9efff96f030926f730a87e7616f0e36772e5c00e6a6dd7718260a4fbf784e -size 121269040 +oid sha256:d8a4cbcd505ab63d93e027978e17f5807152ee526c53e3632bd7bae188fdfcb3 +size 121257384 diff --git a/Source/ThirdParty/Win64/libthirdweb.lib b/Source/ThirdParty/Win64/libthirdweb.lib index 284ff42..e991ac7 100644 --- a/Source/ThirdParty/Win64/libthirdweb.lib +++ b/Source/ThirdParty/Win64/libthirdweb.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daf40656de3681027435b7d3e23c0e83253e9d2e9aa68e97395644b85fc0ca45 -size 91587264 +oid sha256:503601925637c00032b1ffd2b943baa624de58643a8155153eeb5224123958b6 +size 91595106 diff --git a/Source/Thirdweb/Private/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.cpp b/Source/Thirdweb/Private/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.cpp index 970657f..f4229d8 100644 --- a/Source/Thirdweb/Private/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.cpp +++ b/Source/Thirdweb/Private/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.cpp @@ -3,11 +3,12 @@ #include "AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.h" #include "ThirdwebLog.h" -#include "ThirdwebOAuthBrowserUserWidget.h" #include "TimerManager.h" #include "Blueprint/UserWidget.h" +#include "Browser/ThirdwebOAuthBrowserUserWidget.h" + #include "Engine/World.h" #include "Kismet/GameplayStatics.h" @@ -16,7 +17,7 @@ void UAsyncTaskThirdwebLoginWithOAuth::Activate() { - Browser->OnSuccess.AddDynamic(this, &ThisClass::HandleSuccess); + Browser->OnAuthenticated.AddDynamic(this, &ThisClass::HandleAuthenticated); Browser->OnError.AddDynamic(this, &ThisClass::HandleFailed); Browser->AddToViewport(10000); Browser->Authenticate(Wallet); @@ -42,9 +43,14 @@ void UAsyncTaskThirdwebLoginWithOAuth::HandleFailed(const FString& Error) SetReadyToDestroy(); } -void UAsyncTaskThirdwebLoginWithOAuth::HandleSuccess() +void UAsyncTaskThirdwebLoginWithOAuth::HandleAuthenticated(const FString& AuthResult) { + if (FString Error; !Wallet.SignInWithOAuth(AuthResult, Error)) + { + return HandleFailed(Error); + } Success.Broadcast(TEXT("")); Browser->RemoveFromParent(); SetReadyToDestroy(); } + diff --git a/Source/Thirdweb/Private/ThirdwebOAuthBrowserUserWidget.cpp b/Source/Thirdweb/Private/Browser/ThirdwebOAuthBrowserUserWidget.cpp similarity index 71% rename from Source/Thirdweb/Private/ThirdwebOAuthBrowserUserWidget.cpp rename to Source/Thirdweb/Private/Browser/ThirdwebOAuthBrowserUserWidget.cpp index 1087382..9d26121 100644 --- a/Source/Thirdweb/Private/ThirdwebOAuthBrowserUserWidget.cpp +++ b/Source/Thirdweb/Private/Browser/ThirdwebOAuthBrowserUserWidget.cpp @@ -1,12 +1,14 @@ // Copyright (c) 2024 Thirdweb. All Rights Reserved. -#include "ThirdwebOAuthBrowserUserWidget.h" +#include "Browser/ThirdwebOAuthBrowserUserWidget.h" #include "ThirdwebLog.h" -#include "ThirdwebOAuthBrowserWidget.h" #include "Blueprint/WidgetTree.h" +#include "Browser/ThirdwebOAuthBrowserWidget.h" + +#include "Components/Button.h" #include "Components/Overlay.h" #include "Components/OverlaySlot.h" #include "Components/PanelWidget.h" @@ -20,23 +22,25 @@ TSharedRef UThirdwebOAuthBrowserUserWidget::RebuildWidget() UPanelWidget* RootWidget = Cast(GetRootWidget()); + // Construct root widget if needed if (!RootWidget) { RootWidget = WidgetTree->ConstructWidget(UOverlay::StaticClass(), TEXT("RootWidget")); WidgetTree->RootWidget = RootWidget; } + // Construct children if (RootWidget) { + // Construct browser widget Browser = WidgetTree->ConstructWidget(UThirdwebOAuthBrowserWidget::StaticClass(), TEXT("ThirdwebOauthBrowser")); - Browser->OnUrlChanged.AddUniqueDynamic(this, &ThisClass::HandleUrlChanged); + Browser->OnUrlChanged.AddUObject(this, &ThisClass::HandleUrlChanged); + Browser->OnPageLoaded.AddUObject(this, &ThisClass::HandlePageLoaded); UPanelSlot* PanelSlot = RootWidget->AddChild(Browser); if (UOverlaySlot* RootWidgetSlot = Cast(PanelSlot)) { - TW_LOG(Warning, TEXT("ThirdwebOAuthBrowserUserWidget::RebuildWidget()")); RootWidgetSlot->SetHorizontalAlignment(HAlign_Fill); RootWidgetSlot->SetVerticalAlignment(VAlign_Fill); - } } @@ -60,11 +64,11 @@ void UThirdwebOAuthBrowserUserWidget::Authenticate(const FWalletHandle& InAppWal { if (!InAppWallet.IsValid()) { - TW_LOG(Error, TEXT("OAuthBrowserUserWidget::Authenticate::Wallet invalid"));\ + TW_LOG(Error, TEXT("OAuthBrowserUserWidget::Authenticate::Wallet invalid")); return OnError.Broadcast(TEXT("Invalid Wallet")); } Wallet = InAppWallet; - + if (Browser) { FString Error; @@ -77,8 +81,15 @@ void UThirdwebOAuthBrowserUserWidget::Authenticate(const FWalletHandle& InAppWal } } -// ReSharper disable once CppPassValueParameterByConstReference -void UThirdwebOAuthBrowserUserWidget::HandleUrlChanged(FString Url) + +bool UThirdwebOAuthBrowserUserWidget::IsBlank() const +{ + FString Url = Browser->GetUrl(); + + return Url.IsEmpty() || Url.StartsWith(BackendUrlPrefix); +} + +void UThirdwebOAuthBrowserUserWidget::HandleUrlChanged(const FString& Url) { TW_LOG(Verbose, TEXT("OAuthBrowserUserWidget::HandleUrlChanged::%s"), *Url); if (Url.IsEmpty() || Url.StartsWith(BackendUrlPrefix)) @@ -91,17 +102,19 @@ void UThirdwebOAuthBrowserUserWidget::HandleUrlChanged(FString Url) FString Left, Right; if (Url.Split(TEXT("authResult="), &Left, &Right, ESearchCase::IgnoreCase)) { - FString Error; - if (Wallet.SignInWithOAuth(Right, Error)) - { - return OnSuccess.Broadcast(); - } - return OnError.Broadcast(Error); + return OnAuthenticated.Broadcast(Right); } return OnError.Broadcast(TEXT("Failed to match AuthResult in url")); - } - SetVisible(true); + bShouldBeVisible = true; +} + +void UThirdwebOAuthBrowserUserWidget::HandlePageLoaded(const FString& Url) +{ + if (bShouldBeVisible) + { + SetVisible(true); + } } void UThirdwebOAuthBrowserUserWidget::SetVisible(const bool bVisible) @@ -109,18 +122,25 @@ void UThirdwebOAuthBrowserUserWidget::SetVisible(const bool bVisible) // Mobile webview needs to be visible to work if (bVisible) { + if (bCollapseWhenBlank) + { #if PLATFORM_IOS | PLATFORM_ANDROID - SetRenderOpacity(1.0f); + SetRenderOpacity(1.0f); #else - SetVisibility(ESlateVisibility::Visible); + SetVisibility(ESlateVisibility::Visible); #endif + } } else { + bShouldBeVisible = false; + if (bCollapseWhenBlank) + { #if PLATFORM_IOS | PLATFORM_ANDROID - SetRenderOpacity(0.01f); + SetRenderOpacity(0.01f); #else - SetVisibility(ESlateVisibility::Hidden); + SetVisibility(ESlateVisibility::Collapsed); #endif + } } } diff --git a/Source/Thirdweb/Private/ThirdwebOAuthBrowserWidget.cpp b/Source/Thirdweb/Private/Browser/ThirdwebOAuthBrowserWidget.cpp similarity index 58% rename from Source/Thirdweb/Private/ThirdwebOAuthBrowserWidget.cpp rename to Source/Thirdweb/Private/Browser/ThirdwebOAuthBrowserWidget.cpp index 40bfbaa..f100869 100644 --- a/Source/Thirdweb/Private/ThirdwebOAuthBrowserWidget.cpp +++ b/Source/Thirdweb/Private/Browser/ThirdwebOAuthBrowserWidget.cpp @@ -1,10 +1,10 @@ // Copyright (c) 2024 Thirdweb. All Rights Reserved. -#include "ThirdwebOAuthBrowserWidget.h" +#include "Browser/ThirdwebOAuthBrowserWidget.h" #include "ThirdwebLog.h" -#include "Async//Async.h" +#include "Async/Async.h" #include "GenericPlatform/GenericPlatformHttp.h" @@ -14,6 +14,13 @@ #include "SWebBrowser.h" #endif +#define ENSURE_VALID_BROWSER(FunctionName) \ + if (!Browser.IsValid()) \ + { \ + TW_LOG(Error, TEXT("OAuthBrowserWidget::%s::Web browser invalid"), TEXT(FunctionName)); \ + return; \ + } + const FString UThirdwebOAuthBrowserWidget::DummyUrl = TEXT("about:blank"); bool UThirdwebOAuthBrowserWidget::IsPageLoaded() const @@ -25,6 +32,20 @@ bool UThirdwebOAuthBrowserWidget::IsPageLoaded() const #endif } +FString UThirdwebOAuthBrowserWidget::GetUrl() const +{ +#if WITH_CEF + if (Browser.IsValid()) + { + if (FString Url = Browser->GetUrl(); !Url.IsEmpty()) + { + return FGenericPlatformHttp::UrlDecode(Url); + } + } +#endif + return TEXT(""); +} + void UThirdwebOAuthBrowserWidget::ReleaseSlateResources(const bool bReleaseChildren) { Super::ReleaseSlateResources(bReleaseChildren); @@ -44,12 +65,8 @@ const FText UThirdwebOAuthBrowserWidget::GetPaletteCategory() void UThirdwebOAuthBrowserWidget::Authenticate(const FString& OAuthLoginUrl) { #if WITH_CEF - if (!Browser.IsValid()) - { - TW_LOG(Error, TEXT("OAuthBrowserWidget::Authenticate::Web browser invalid")); - return; - } - TW_LOG(Log, TEXT("OAuthBrowserWidget::Authenticate::Loading %s"), *OAuthLoginUrl); + ENSURE_VALID_BROWSER("Authenticate") + TW_LOG(Verbose, TEXT("OAuthBrowserWidget::Authenticate::Loading %s"), *OAuthLoginUrl); Browser->LoadURL(OAuthLoginUrl); #endif } @@ -57,21 +74,25 @@ void UThirdwebOAuthBrowserWidget::Authenticate(const FString& OAuthLoginUrl) void UThirdwebOAuthBrowserWidget::HandleUrlChanged(const FText& InUrl) { #if WITH_CEF + ENSURE_VALID_BROWSER("HandleUrlChanged") FString Url = InUrl.ToString(); - TW_LOG(Log, TEXT("UThirdwebOAuthBrowserWidget::HandleUrlChanged:%s"), *Url); + TW_LOG(Verbose, TEXT("UThirdwebOAuthBrowserWidget::HandleUrlChanged:%s"), *Url); if (Url.IsEmpty()) { Url = Browser->GetUrl(); } - TW_LOG(Log, TEXT("UThirdwebOAuthBrowserWidget::HandleUrlChanged:%s"), *Url); + TW_LOG(Verbose, TEXT("UThirdwebOAuthBrowserWidget::HandleUrlChanged:%s"), *Url); // Ensure this code runs on the game thread - AsyncTask(ENamedThreads::GameThread, [&, Url]() - { - if (IsInGameThread()) - { - OnUrlChanged.Broadcast(FGenericPlatformHttp::UrlDecode(Url)); - } - }); + AsyncTask(ENamedThreads::GameThread, [&, Url]() { if (IsInGameThread()) OnUrlChanged.Broadcast(FGenericPlatformHttp::UrlDecode(Url)); }); +#endif +} + +void UThirdwebOAuthBrowserWidget::HandleOnLoadComplete() +{ +#if WITH_CEF + ENSURE_VALID_BROWSER("HandleOnLoadComplete") + FString Url = Browser->GetUrl(); + AsyncTask(ENamedThreads::GameThread, [&, Url]() { if (IsInGameThread()) OnPageLoaded.Broadcast(FGenericPlatformHttp::UrlDecode(Url)); }); #endif } @@ -85,6 +106,7 @@ TSharedRef UThirdwebOAuthBrowserWidget::RebuildWidget() .ShowControls(false) .SupportsTransparency(bSupportsTransparency) .ShowInitialThrobber(bShowInitialThrobber) + .OnLoadCompleted(BIND_UOBJECT_DELEGATE(FSimpleDelegate, HandleOnLoadComplete)) .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleUrlChanged)); return Browser.ToSharedRef(); diff --git a/Source/Thirdweb/Private/ThirdwebInternal.cpp b/Source/Thirdweb/Private/ThirdwebInternal.cpp new file mode 100644 index 0000000..2cc3e83 --- /dev/null +++ b/Source/Thirdweb/Private/ThirdwebInternal.cpp @@ -0,0 +1,68 @@ +#include "ThirdwebInternal.h" + +#include "HttpModule.h" +#include "ThirdwebRuntimeSettings.h" +#include "ThirdwebUtils.h" + +#include "Dom/JsonObject.h" + +#include "Interfaces/IPluginManager.h" + +#include "Kismet/GameplayStatics.h" + +#include "Policies/CondensedJsonPrintPolicy.h" + +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonWriter.h" + +FString FThirdwebAnalytics::JsonObjectToString(const TSharedPtr& JsonObject) +{ + FString Out; + const TSharedRef>> Writer = TJsonWriterFactory>::Create(&Out); + FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); + return Out; +} + +FString FThirdwebAnalytics::GetPluginVersion() +{ + if (const TSharedPtr Plugin = IPluginManager::Get().FindPlugin(TEXT("Thirdweb")); Plugin.IsValid()) + { + return Plugin->GetDescriptor().VersionName; + } + return "0.0.0"; +} + +void FThirdwebAnalytics::SendConnectEvent(const FString& Wallet, const FString& Type) +{ + const UThirdwebRuntimeSettings* Settings = UThirdwebRuntimeSettings::Get(); + if (!Settings->bSendAnalytics || (Settings->BundleID.IsEmpty() && Settings->ClientID.IsEmpty() && Settings->SecretKey.IsEmpty())) + { + return; + } + FHttpModule& HttpModule = FHttpModule::Get(); + const TSharedRef Request = HttpModule.CreateRequest(); + Request->SetVerb("POST"); + Request->SetURL("https://c.thirdweb.com/event"); + Request->SetHeader("Content-Type", "application/json"); + Request->SetHeader("x-sdk-name", "UnrealEngineSDK"); + Request->SetHeader("x-sdk-os", UGameplayStatics::GetPlatformName()); + Request->SetHeader("x-sdk-platform", "unreal-engine"); + Request->SetHeader("x-sdk-version", GetPluginVersion()); + if (!Settings->SecretKey.IsEmpty()) + { + Request->SetHeader("x-client-id", ThirdwebUtils::GetClientIdFromSecretKey(Settings->SecretKey)); + } else + { + Request->SetHeader("x-client-id", Settings->ClientID); + Request->SetHeader("x-bundle-id", Settings->BundleID); + } + Request->SetTimeout(5.0f); + + TSharedPtr JsonObject = MakeShareable(new FJsonObject); + JsonObject->SetStringField(TEXT("source"), TEXT("connectWallet")); + JsonObject->SetStringField(TEXT("action"), TEXT("connect")); + JsonObject->SetStringField(TEXT("walletAddress"), Wallet); + JsonObject->SetStringField(TEXT("walletType"), Type); + Request->SetContentAsString(JsonObjectToString(JsonObject)); + Request->ProcessRequest(); +} diff --git a/Source/Thirdweb/Private/ThirdwebRuntimeSettings.cpp b/Source/Thirdweb/Private/ThirdwebRuntimeSettings.cpp index bdc8dc4..e46d0f4 100644 --- a/Source/Thirdweb/Private/ThirdwebRuntimeSettings.cpp +++ b/Source/Thirdweb/Private/ThirdwebRuntimeSettings.cpp @@ -7,4 +7,5 @@ UThirdwebRuntimeSettings::UThirdwebRuntimeSettings() { AuthenticationMethod = EThirdwebAuthenticationMethod::ClientID; + bSendAnalytics = true; } diff --git a/Source/Thirdweb/Private/ThirdwebWalletHandle.cpp b/Source/Thirdweb/Private/ThirdwebWalletHandle.cpp index 9132d22..98b8e0a 100644 --- a/Source/Thirdweb/Private/ThirdwebWalletHandle.cpp +++ b/Source/Thirdweb/Private/ThirdwebWalletHandle.cpp @@ -4,6 +4,7 @@ #include "Thirdweb.h" #include "ThirdwebCommon.h" +#include "ThirdwebInternal.h" #include "ThirdwebMacros.h" #include "ThirdwebRuntimeSettings.h" #include "ThirdwebSigner.h" @@ -62,7 +63,7 @@ bool FWalletHandle::CreateInAppEmailWallet(const FString& Email, FWalletHandle& nullptr ).AssignResult(Error)) { - Wallet = FWalletHandle(FWalletHandle::InApp, Error); + Wallet = FWalletHandle(InApp, Error); Error.Empty(); return true; } @@ -83,7 +84,7 @@ bool FWalletHandle::CreateInAppOAuthWallet(const EThirdwebOAuthProvider Provider TO_RUST_STRING(ThirdwebUtils::ToString(Provider)) ).AssignResult(Error)) { - Wallet = FWalletHandle(FWalletHandle::InApp, Error); + Wallet = FWalletHandle(InApp, Error); Error.Empty(); return true; } @@ -106,7 +107,8 @@ bool FWalletHandle::CreateSmartWallet(const int64 ChainID, const bool bGasless, TO_RUST_STRING(AccountOverride) ).AssignResult(Error)) { - SmartWallet = FWalletHandle(FWalletHandle::Smart, Error); + SmartWallet = FWalletHandle(Smart, Error); + FThirdwebAnalytics::SendConnectEvent(SmartWallet.ToAddress(), SmartWallet.GetTypeString()); Error.Empty(); return true; } @@ -146,7 +148,12 @@ bool FWalletHandle::VerifyOTP(const FString& OTP, bool& CanRetry, FString& Error { if (Type == InApp) { - return Thirdweb::in_app_wallet_verify_otp(ID, TO_RUST_STRING(OTP)).AssignRetryResult(CanRetry, Error, true); + if (Thirdweb::in_app_wallet_verify_otp(ID, TO_RUST_STRING(OTP)).AssignRetryResult(CanRetry, Error, true)) + { + FThirdwebAnalytics::SendConnectEvent(ToAddress(), GetTypeString()); + return true; + } + return false; } Error = TEXT("Wallet type must be InAppWallet for OTP action"); return false; @@ -189,7 +196,13 @@ bool FWalletHandle::SignInWithOAuth(const FString& AuthResult, FString& Error) { Result = FGenericPlatformHttp::UrlDecode(AuthResult); } - return Thirdweb::in_app_wallet_sign_in_with_oauth(ID, TO_RUST_STRING(Result)).AssignResult(Error); + + if (Thirdweb::in_app_wallet_sign_in_with_oauth(ID, TO_RUST_STRING(Result)).AssignResult(Error)) + { + FThirdwebAnalytics::SendConnectEvent(ToAddress(), GetTypeString()); + return true; + } + return false; } Error = TEXT("Wallet type must be InAppWallet for OAuth action"); return false; diff --git a/Source/Thirdweb/Public/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.h b/Source/Thirdweb/Public/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.h index c2c1a10..5a6c732 100644 --- a/Source/Thirdweb/Public/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.h +++ b/Source/Thirdweb/Public/AsyncTasks/AsyncTaskThirdwebLoginWithOAuth.h @@ -44,5 +44,5 @@ class THIRDWEB_API UAsyncTaskThirdwebLoginWithOAuth : public UBlueprintAsyncActi void HandleFailed(const FString& Error); UFUNCTION() - void HandleSuccess(); + void HandleAuthenticated(const FString& AuthResult); }; diff --git a/Source/Thirdweb/Public/Browser/ThirdwebOAuthBrowserUserWidget.h b/Source/Thirdweb/Public/Browser/ThirdwebOAuthBrowserUserWidget.h new file mode 100644 index 0000000..4ffb40e --- /dev/null +++ b/Source/Thirdweb/Public/Browser/ThirdwebOAuthBrowserUserWidget.h @@ -0,0 +1,71 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#pragma once + +#include "ThirdwebWalletHandle.h" + +#include "Blueprint/UserWidget.h" + +#include "ThirdwebOAuthBrowserUserWidget.generated.h" + +UCLASS(DisplayName="OAuth Browser") +class THIRDWEB_API UThirdwebOAuthBrowserUserWidget : public UUserWidget +{ + GENERATED_BODY() + +public: + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAuthenticatedDelegate, const FString&, AuthResult); + UPROPERTY(BlueprintAssignable, Category="Thirdweb|OAuth Browser") + FOnAuthenticatedDelegate OnAuthenticated; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnErrorDelegate, const FString&, Error); + UPROPERTY(BlueprintAssignable, Category="Thirdweb|OAuth Browser") + FOnErrorDelegate OnError; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FUrlDelegate, const FString&, URL); + UPROPERTY(BlueprintAssignable, DisplayName="On URL Changed", Category="Thirdweb|OAuth Browser") + FUrlDelegate OnUrlChanged; + + UPROPERTY(BlueprintAssignable, Category="Thirdweb|OAuth Browser") + FUrlDelegate OnPageLoaded; + +protected: + /** Automatically collapse the widget when the page is blank */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Browser") + bool bCollapseWhenBlank = true; + + /** Automatically authenticate on Construct. Only works when created with a Wallet */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Browser") + bool bAuthenticateOnConstruct = false; + + UPROPERTY(BlueprintReadOnly, Transient, Category="Internal", meta=(ExposeOnSpawn=true)) + FWalletHandle Wallet; + +private: + UPROPERTY(Transient) + class UThirdwebOAuthBrowserWidget* Browser = nullptr; + + static const FString BackendUrlPrefix; + bool bShouldBeVisible = false; + +public: + virtual TSharedRef RebuildWidget() override; + virtual void OnWidgetRebuilt() override; + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + +protected: + void SetVisible(const bool bVisible); + virtual void HandleUrlChanged(const FString& Url); + virtual void HandlePageLoaded(const FString& Url); + +public: + UFUNCTION(BlueprintCallable, Category="Thirdweb|OAuth Browser") + void Authenticate(const FWalletHandle& InAppWallet); + + /** Returns true if the page content is blank. Normally the case at startup, and mid-oauth flow */ + UFUNCTION(BlueprintPure, Category="Thirdweb|OAuth Browser") + bool IsBlank() const; +}; \ No newline at end of file diff --git a/Source/Thirdweb/Public/ThirdwebOAuthBrowserWidget.h b/Source/Thirdweb/Public/Browser/ThirdwebOAuthBrowserWidget.h similarity index 79% rename from Source/Thirdweb/Public/ThirdwebOAuthBrowserWidget.h rename to Source/Thirdweb/Public/Browser/ThirdwebOAuthBrowserWidget.h index 14b3b82..e4f18f1 100644 --- a/Source/Thirdweb/Public/ThirdwebOAuthBrowserWidget.h +++ b/Source/Thirdweb/Public/Browser/ThirdwebOAuthBrowserWidget.h @@ -11,9 +11,9 @@ #include "ThirdwebOAuthBrowserWidget.generated.h" -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnUrlChanged, FString, InUrl); +DECLARE_MULTICAST_DELEGATE_OneParam(FSimpleStringDelegate, const FString&); -UCLASS() +UCLASS(NotBlueprintable, NotBlueprintType, Hidden) class THIRDWEB_API UThirdwebOAuthBrowserWidget : public UWidget { GENERATED_BODY() @@ -28,8 +28,10 @@ class THIRDWEB_API UThirdwebOAuthBrowserWidget : public UWidget //~ End Public Overrides virtual void HandleUrlChanged(const FText& InUrl); + virtual void HandleOnLoadComplete(); bool IsPageLoaded() const; - + FString GetUrl() const; + UFUNCTION(BlueprintCallable, Category="Thirdweb|OAuth Browser") void Authenticate(const FString& OAuthLoginUrl); @@ -38,8 +40,9 @@ class THIRDWEB_API UThirdwebOAuthBrowserWidget : public UWidget virtual void OnWidgetRebuilt() override; public: - FOnUrlChanged OnUrlChanged; - + FSimpleStringDelegate OnUrlChanged; + FSimpleStringDelegate OnPageLoaded; + static const FString DummyUrl; private: diff --git a/Source/Thirdweb/Public/Thirdweb.h b/Source/Thirdweb/Public/Thirdweb.h index 9ebbb1c..c965520 100644 --- a/Source/Thirdweb/Public/Thirdweb.h +++ b/Source/Thirdweb/Public/Thirdweb.h @@ -88,5 +88,6 @@ namespace Thirdweb FFIResult is_valid_address(const char* address, bool check_checksum); FFIResult to_checksummed_address(const char* address); FFIResult is_valid_private_key(const char* private_key); + FFIResult compute_client_id_from_secret_key(const char *secret_key); } } diff --git a/Source/Thirdweb/Public/ThirdwebInternal.h b/Source/Thirdweb/Public/ThirdwebInternal.h new file mode 100644 index 0000000..3737360 --- /dev/null +++ b/Source/Thirdweb/Public/ThirdwebInternal.h @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#pragma once + +// Created as a struct instead of a namespaces so the functions are not exported. +struct FThirdwebAnalytics +{ + + static void SendConnectEvent(const FString& Wallet, const FString& Type); + +private: + static FString JsonObjectToString(const TSharedPtr& JsonObject); + static FString GetPluginVersion(); +}; + diff --git a/Source/Thirdweb/Public/ThirdwebOAuthBrowserUserWidget.h b/Source/Thirdweb/Public/ThirdwebOAuthBrowserUserWidget.h deleted file mode 100644 index 04c6d39..0000000 --- a/Source/Thirdweb/Public/ThirdwebOAuthBrowserUserWidget.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2024 Thirdweb. All Rights Reserved. - -#pragma once - -#include "ThirdwebWalletHandle.h" - -#include "Blueprint/UserWidget.h" - -#include "ThirdwebOAuthBrowserUserWidget.generated.h" - -UCLASS() -class THIRDWEB_API UThirdwebOAuthBrowserUserWidget : public UUserWidget -{ - GENERATED_BODY() - -public: - virtual TSharedRef RebuildWidget() override; - virtual void OnWidgetRebuilt() override; -#if WITH_EDITOR - virtual const FText GetPaletteCategory() override; -#endif - - UFUNCTION(BlueprintCallable, Category="Thirdweb|OAuth Browser") - void Authenticate(const FWalletHandle& InAppWallet); - - DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSuccess); - UPROPERTY(BlueprintAssignable, Category="Thirdweb|OAuth Browser") - FOnSuccess OnSuccess; - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnError, const FString&, Error); - UPROPERTY(BlueprintAssignable, Category="Thirdweb|OAuth Browser") - FOnError OnError; - -private: - UPROPERTY(Transient) - class UThirdwebOAuthBrowserWidget* Browser = nullptr; - - UPROPERTY(Transient) - FWalletHandle Wallet; - - UFUNCTION() - void HandleUrlChanged(FString Url); - - static const FString BackendUrlPrefix; - - void SetVisible(const bool bVisible); -}; \ No newline at end of file diff --git a/Source/Thirdweb/Public/ThirdwebRuntimeSettings.h b/Source/Thirdweb/Public/ThirdwebRuntimeSettings.h index c1e5cc7..54b69fc 100644 --- a/Source/Thirdweb/Public/ThirdwebRuntimeSettings.h +++ b/Source/Thirdweb/Public/ThirdwebRuntimeSettings.h @@ -45,6 +45,10 @@ class THIRDWEB_API UThirdwebRuntimeSettings : public UDeveloperSettings /** Optional array of engine signers stored globally for convenience */ UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category=Config) TArray EngineSigners; + + /** Optional array of engine signers stored globally for convenience */ + UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category=Config) + bool bSendAnalytics; UFUNCTION(BlueprintPure, Category="Thirdweb", DisplayName="Get Thirdweb Runtime Settings") static const UThirdwebRuntimeSettings* Get() { return GetDefault(); } diff --git a/Source/Thirdweb/Public/ThirdwebUtils.h b/Source/Thirdweb/Public/ThirdwebUtils.h index 797cfda..45cda7e 100644 --- a/Source/Thirdweb/Public/ThirdwebUtils.h +++ b/Source/Thirdweb/Public/ThirdwebUtils.h @@ -3,6 +3,7 @@ #pragma once #include "Thirdweb.h" +#include "ThirdwebCommon.h" #include "ThirdwebMacros.h" #include "ThirdwebRuntimeSettings.h" @@ -49,7 +50,7 @@ namespace ThirdwebUtils * @param Provider The EThirdwebOAuthProvider enum value to convert. * @return The FText representation of the specified EThirdwebOAuthProvider, or "Invalid" if the provider is not recognized. */ - inline FText ToText(const EThirdwebOAuthProvider Provider) + static FText ToText(const EThirdwebOAuthProvider Provider) { static TMap Map = { {EThirdwebOAuthProvider::Google, LOCTEXT("Google", "Google")}, @@ -65,7 +66,15 @@ namespace ThirdwebUtils * @param Provider The EThirdwebOAuthProvider enum value to convert. * @return The string representation of the specified EThirdwebOAuthProvider. */ - inline FString ToString(const EThirdwebOAuthProvider Provider) { return ToText(Provider).ToString(); } + static FString ToString(const EThirdwebOAuthProvider Provider) { return ToText(Provider).ToString(); } + + /** + * Derives a client ID from the provided secret key. + * + * @param SecretKey The secret key used to compute the client ID. + * @return The computed client ID. + */ + static FString GetClientIdFromSecretKey(const FString& SecretKey) { return Thirdweb::compute_client_id_from_secret_key(TO_RUST_STRING(SecretKey)).GetOutput(); } } diff --git a/Source/Thirdweb/Public/ThirdwebWalletHandle.h b/Source/Thirdweb/Public/ThirdwebWalletHandle.h index 30ccc78..7c8b63b 100644 --- a/Source/Thirdweb/Public/ThirdwebWalletHandle.h +++ b/Source/Thirdweb/Public/ThirdwebWalletHandle.h @@ -9,7 +9,7 @@ struct FSigner; /** Unique handle that can be used to distinguish wallets */ USTRUCT(BlueprintType, Blueprintable) -struct FWalletHandle +struct THIRDWEB_API FWalletHandle { GENERATED_BODY() @@ -56,7 +56,7 @@ struct FWalletHandle /** Get the private key of this handle (PrivateKey wallet only) */ FString GetPrivateKey() const; - + /** * Creates an in-app email wallet. * @@ -140,7 +140,6 @@ struct FWalletHandle /** Get the active signers of a smart wallet */ bool GetActiveSigners(TArray& Signers, FString& Error); - /** sign a message */ FString Sign(const FString& Message) const; @@ -154,20 +153,11 @@ struct FWalletHandle return Type != Other.Type || ID != Other.ID; } - FString ToString() const + FString ToString() const { return IsValid() ? FString::Printf(TEXT("%sWallet:%lld"), GetTypeString(), ID) : TEXT("INVALID"); } + + const TCHAR* GetTypeString() const { - return IsValid() - ? FString::Printf( - TEXT("%sWallet:%lld"), - Type == PrivateKey - ? TEXT("PrivateKey") - : Type == InApp - ? TEXT("InApp") - : Type == Smart - ? TEXT("Smart") - : TEXT("Unknown"), - ID) - : TEXT("INVALID"); + return IsValid() ? Type == PrivateKey ? TEXT("privateKey") : Type == InApp ? TEXT("inApp") : Type == Smart ? TEXT("smart") : TEXT("unknown") : TEXT("invalid"); } private: @@ -177,7 +167,7 @@ struct FWalletHandle // The wallet handle id UPROPERTY(Transient) int64 ID = 0; - + friend uint32 GetTypeHash(const FWalletHandle& InHandle) { return GetTypeHash(InHandle.ID); diff --git a/Source/Thirdweb/Thirdweb.Build.cs b/Source/Thirdweb/Thirdweb.Build.cs index fe7d131..2745fa9 100644 --- a/Source/Thirdweb/Thirdweb.Build.cs +++ b/Source/Thirdweb/Thirdweb.Build.cs @@ -3,6 +3,8 @@ using UnrealBuildTool; using System.IO; +// ReSharper disable UseCollectionExpression + public class Thirdweb : ModuleRules { private bool IsWin64 => Target.Platform.Equals(UnrealTargetPlatform.Win64); @@ -15,7 +17,7 @@ public class Thirdweb : ModuleRules ; private bool IsApple => Target.Platform.IsInGroup(UnrealPlatformGroup.Apple); - + private bool IsAndroid => Target.Platform.Equals(UnrealTargetPlatform.Android); private bool IsMobile => IsIOSIsh || IsAndroid; @@ -28,7 +30,7 @@ public class Thirdweb : ModuleRules public Thirdweb(ReadOnlyTargetRules target) : base(target) { - PrivateDependencyModuleNames.AddRange(new [] { "Boost" }); + PrivateDependencyModuleNames.Add("Boost"); PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; #if UE_5_3_OR_LATER @@ -38,13 +40,13 @@ public Thirdweb(ReadOnlyTargetRules target) : base(target) #if UE_5_0_OR_LATER PublicDefinitions.Add("WITH_CEF=1"); PrivateDependencyModuleNames.Add("WebBrowser"); - + // Copied from WebBrowserWidget if (target.bBuildEditor || IsAndroid || IsIOSIsh) { // WebBrowserTexture required for cooking Android - PrivateIncludePathModuleNames.AddRange(new [] { "WebBrowserTexture" }); - PrivateDependencyModuleNames.AddRange(new [] { "WebBrowserTexture" }); + PrivateIncludePathModuleNames.Add("WebBrowserTexture"); + PrivateDependencyModuleNames.Add("WebBrowserTexture"); if (IsAndroid) { @@ -83,13 +85,14 @@ public Thirdweb(ReadOnlyTargetRules target) : base(target) { PrivateDependencyModuleNames.Add("Launch"); } - + PublicDependencyModuleNames.AddRange(new[] { // Standard deps "Core", "CoreUObject", "Engine", + "Projects", // plugin settings deps "DeveloperSettings", // Thirdweb Engine call deps @@ -102,14 +105,15 @@ public Thirdweb(ReadOnlyTargetRules target) : base(target) "Slate", "SlateCore" }); - + + // ReSharper disable once InvertIf // Copied from WebBrowserWidget if (target.bBuildEditor) { - // @TODO: UnrealEd Needed for the triangulation code used for sprites (but only in editor mode) - // @TOOD: Try to move the code dependent on the triangulation code to the editor-only module - PrivateIncludePathModuleNames.AddRange(new [] { "UnrealEd" }); - PrivateDependencyModuleNames.AddRange(new [] { "EditorFramework", "UnrealEd" }); + // TODO::UnrealEd Needed for the triangulation code used for sprites (but only in editor mode) + // Try to move the code dependent on the triangulation code to the editor-only module + PrivateIncludePathModuleNames.Add("UnrealEd"); + PrivateDependencyModuleNames.AddRange(new[] { "EditorFramework", "UnrealEd" }); } } } \ No newline at end of file diff --git a/Thirdweb.uplugin b/Thirdweb.uplugin index 80b008f..d5a1dd4 100644 --- a/Thirdweb.uplugin +++ b/Thirdweb.uplugin @@ -14,6 +14,7 @@ "IsBetaVersion": false, "IsExperimentalVersion": false, "Installed": false, + "EditorCustomVirtualPath": "Thirdweb", "Modules": [ { "Name": "Thirdweb",