diff --git a/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset b/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset index 43d5d4a9..4d8fb4be 100644 Binary files a/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset and b/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset differ diff --git a/Content/PackagedResources/index.uasset b/Content/PackagedResources/index.uasset index e1c12e1c..169ee98a 100644 Binary files a/Content/PackagedResources/index.uasset and b/Content/PackagedResources/index.uasset differ diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp new file mode 100644 index 00000000..4e705769 --- /dev/null +++ b/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp @@ -0,0 +1,103 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Immutable/Actions/ImtblConnectImxAsyncAction.h" + +#include "Immutable/ImmutablePassport.h" +#include "Immutable/ImmutableSubsystem.h" +#include "Immutable/Misc/ImtblLogging.h" + + +UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::Login(UObject* WorldContextObject, bool UseCachedSession) +{ + UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject(); + + PassportInitBlueprintNode->WorldContextObject = WorldContextObject; + PassportInitBlueprintNode->bUseCachedSession = UseCachedSession; + PassportInitBlueprintNode->bIsConnectImx = false; + + return PassportInitBlueprintNode; +} + +UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::ConnectImx(UObject* WorldContextObject, bool UseCachedSession) +{ + UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject(); + + PassportInitBlueprintNode->WorldContextObject = WorldContextObject; + PassportInitBlueprintNode->bUseCachedSession = UseCachedSession; + PassportInitBlueprintNode->bIsConnectImx = true; + + return PassportInitBlueprintNode; +} + +UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::LoginPKCE(UObject* WorldContextObject) +{ + UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject(); + + PassportInitBlueprintNode->WorldContextObject = WorldContextObject; + PassportInitBlueprintNode->bIsConnectImx = false; + PassportInitBlueprintNode->bIsPKCE = true; + + return PassportInitBlueprintNode; +} + +UImtblConnectionAsyncActions* UImtblConnectionAsyncActions::ConnectImxPKCE(UObject* WorldContextObject) +{ + UImtblConnectionAsyncActions* PassportInitBlueprintNode = NewObject(); + + PassportInitBlueprintNode->WorldContextObject = WorldContextObject; + PassportInitBlueprintNode->bIsConnectImx = true; + PassportInitBlueprintNode->bIsPKCE = true; + + return PassportInitBlueprintNode; +} + +void UImtblConnectionAsyncActions::Activate() +{ + if (!WorldContextObject || !WorldContextObject->GetWorld()) + { + FString Error = "Connect failed due to missing world or world context object."; + IMTBL_WARN("%s", *Error) + Failed.Broadcast(Error); + + return; + } + + GetSubsystem()->WhenReady(this, &UImtblConnectionAsyncActions::DoConnect); +} + +void UImtblConnectionAsyncActions::DoConnect(TWeakObjectPtr JSConnector) +{ + auto Passport = GetSubsystem()->GetPassport(); + + if (Passport.IsValid()) + { + if (bIsPKCE) + { +#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC + Passport->ConnectPKCE(bIsConnectImx, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject + (this, &UImtblConnectionAsyncActions::OnConnect)); +#endif + } + else + { + Passport->Connect(bIsConnectImx, bUseCachedSession, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject + (this, &UImtblConnectionAsyncActions::OnConnect)); + } + } + else + { + IMTBL_ERR("Passport was not valid while trying to connect") + } +} + +void UImtblConnectionAsyncActions::OnConnect(FImmutablePassportResult Result) +{ + if (Result.Success) + { + Success.Broadcast(TEXT("")); + } + else + { + Failed.Broadcast(Result.Message); + } +} diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectAsyncAction.cpp deleted file mode 100644 index 29f9dfc8..00000000 --- a/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectAsyncAction.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "Immutable/Actions/ImtblPassportConnectAsyncAction.h" - -#include "Immutable/ImmutablePassport.h" -#include "Immutable/ImmutableSubsystem.h" -#include "Immutable/Misc/ImtblLogging.h" - -UImtblPassportConnectAsyncAction * -UImtblPassportConnectAsyncAction::Connect(UObject *WorldContextObject) { - UImtblPassportConnectAsyncAction *PassportInitBlueprintNode = - NewObject(); - PassportInitBlueprintNode->WorldContextObject = WorldContextObject; - return PassportInitBlueprintNode; -} - -void UImtblPassportConnectAsyncAction::Activate() { - if (!WorldContextObject || !WorldContextObject->GetWorld()) { - FString Err = - "Connect failed due to missing world or world context object."; - IMTBL_WARN("%s", *Err) - Failed.Broadcast(Err); - return; - } - - GetSubsystem()->WhenReady( - this, - &UImtblPassportConnectAsyncAction::DoConnect); //, /* timoutSec */ 15.0f); -} - -void UImtblPassportConnectAsyncAction::DoConnect( - TWeakObjectPtr JSConnector) { - // Get Passport - auto Passport = GetSubsystem()->GetPassport(); - // Run Connect - Passport->Connect( - UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject( - this, &UImtblPassportConnectAsyncAction::OnConnect)); -} - -void UImtblPassportConnectAsyncAction::OnConnect( - FImmutablePassportResult Result) { - if (Result.Success) { - Success.Broadcast(TEXT("")); - } else { - Failed.Broadcast(Result.Message); - } -} diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.cpp deleted file mode 100644 index ad95651f..00000000 --- a/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.h" - -#include "Immutable/ImmutablePassport.h" -#include "Immutable/ImmutableSubsystem.h" -#include "Immutable/Misc/ImtblLogging.h" - -UImtblPassportConnectPKCEAsyncAction * -UImtblPassportConnectPKCEAsyncAction::ConnectPKCE(UObject *WorldContextObject) { - UImtblPassportConnectPKCEAsyncAction *PassportInitBlueprintNode = - NewObject(); - PassportInitBlueprintNode->WorldContextObject = WorldContextObject; - return PassportInitBlueprintNode; -} - -void UImtblPassportConnectPKCEAsyncAction::Activate() { - if (!WorldContextObject || !WorldContextObject->GetWorld()) { - FString Err = - "Connect failed due to missing world or world context object."; - IMTBL_WARN("%s", *Err) - Failed.Broadcast(Err); - return; - } - - GetSubsystem()->WhenReady( - this, &UImtblPassportConnectPKCEAsyncAction::DoConnectPKCE); -} - -void UImtblPassportConnectPKCEAsyncAction::DoConnectPKCE( - TWeakObjectPtr JSConnector) { - // Get Passport - auto Passport = GetSubsystem()->GetPassport(); -#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC - // Run Connect - Passport->ConnectPKCE( - UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject( - this, &UImtblPassportConnectPKCEAsyncAction::OnConnectPKCE)); -#endif -} - -void UImtblPassportConnectPKCEAsyncAction::OnConnectPKCE( - FImmutablePassportResult Result) { - if (Result.Success) { - Success.Broadcast(TEXT("")); - } else { - Failed.Broadcast(Result.Message); - } -} diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectSilentAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectSilentAsyncAction.cpp deleted file mode 100644 index 91762cb4..00000000 --- a/Source/Immutable/Private/Immutable/Actions/ImtblPassportConnectSilentAsyncAction.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include "Immutable/Actions/ImtblPassportConnectSilentAsyncAction.h" - -#include "Immutable/ImmutablePassport.h" -#include "Immutable/ImmutableSubsystem.h" -#include "Immutable/Misc/ImtblLogging.h" - - -UImtblPassportConnectSilentAsyncAction * UImtblPassportConnectSilentAsyncAction::ConnectSilent(UObject *WorldContextObject) -{ - UImtblPassportConnectSilentAsyncAction *PassportInitBlueprintNode = NewObject(); - - PassportInitBlueprintNode->WorldContextObject = WorldContextObject; - - return PassportInitBlueprintNode; -} - -void UImtblPassportConnectSilentAsyncAction::Activate() -{ - if (!WorldContextObject || !WorldContextObject->GetWorld()) - { - FString Err = "Reconnect failed due to missing world or world context object."; - IMTBL_WARN("%s", *Err) - OnFailure.Broadcast(Err); - return; - } - - GetSubsystem()->WhenReady(this, &UImtblPassportConnectSilentAsyncAction:: DoConnectSilent); -} - -void UImtblPassportConnectSilentAsyncAction::DoConnectSilent(TWeakObjectPtr JSConnector) -{ - auto Passport = GetSubsystem()->GetPassport(); - - if (Passport.IsValid()) - { - Passport->ConnectSilent(UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UImtblPassportConnectSilentAsyncAction::OnConnectSilentResponse)); - } -} - -void UImtblPassportConnectSilentAsyncAction::OnConnectSilentResponse(FImmutablePassportResult Result) -{ - if (Result.Success) - { - IMTBL_LOG("Reconnect success") - OnSuccess.Broadcast(Result.Message); - } - else - { - IMTBL_LOG("Reconnect failed") - OnFailure.Broadcast(Result.Message); - } -} diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxBatchNftTransferAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxBatchNftTransferAsyncAction.cpp index c2edcab5..0661357d 100644 --- a/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxBatchNftTransferAsyncAction.cpp +++ b/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxBatchNftTransferAsyncAction.cpp @@ -4,6 +4,7 @@ #include "Immutable/ImmutablePassport.h" #include "Immutable/ImmutableSubsystem.h" +#include "Immutable/ImmutableResponses.h" #include "Immutable/Misc/ImtblLogging.h" UImmutablePassportImxBatchNftTransferAsyncAction * diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxRegisterOffchainAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxRegisterOffchainAsyncAction.cpp index 6f4bc034..1a18f7eb 100644 --- a/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxRegisterOffchainAsyncAction.cpp +++ b/Source/Immutable/Private/Immutable/Actions/ImtblPassportImxRegisterOffchainAsyncAction.cpp @@ -2,6 +2,7 @@ #include "Immutable/ImmutablePassport.h" #include "Immutable/ImmutableSubsystem.h" +#include "Immutable/ImmutableResponses.h" #include "Immutable/Misc/ImtblLogging.h" diff --git a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp index 954a3ca0..d68acd36 100644 --- a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp +++ b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp @@ -3,6 +3,7 @@ #include "Immutable/ImmutablePassport.h" #include "Immutable/Misc/ImtblLogging.h" +#include "Immutable/ImmutableResponses.h" #include "ImtblJSConnector.h" #include "JsonObjectConverter.h" #include "Policies/CondensedJsonPrintPolicy.h" @@ -69,13 +70,13 @@ FString FImxBatchNftTransferRequest::ToJsonString() const return OutString; } -TOptional FImmutablePassportConnectData::FromJsonString(const FString& JsonObjectString) +TOptional FImmutablePassportInitDeviceFlowData::FromJsonString(const FString& JsonObjectString) { - FImmutablePassportConnectData PassportConnect; + FImmutablePassportInitDeviceFlowData PassportConnect; if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonObjectString, &PassportConnect, 0, 0)) { IMTBL_WARN("Could not parse response from JavaScript into the expected " "Passport connect format") - return TOptional(); + return TOptional(); } return PassportConnect; } @@ -134,8 +135,6 @@ FString FImmutablePassportZkEvmGetBalanceData::ToJsonString() const return OutString; } -// @param Environment can be one of ImmutablePassportAction::EnvSandbox or -// ImmutablePassportAction::EnvProduction void UImmutablePassport::Initialize(const FImmutablePassportInitData& Data, const FImtblPassportResponseDelegate& ResponseDelegate) { @@ -143,32 +142,60 @@ void UImmutablePassport::Initialize(const FImmutablePassportInitData& Data, InitData = Data; - CallJS(ImmutablePassportAction::Initialize, InitData.ToJsonString(), ResponseDelegate, + CallJS(ImmutablePassportAction::INIT, InitData.ToJsonString(), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnInitializeResponse), false); } -void UImmutablePassport::Logout(const FImtblPassportResponseDelegate& ResponseDelegate) +void UImmutablePassport::Connect(bool IsConnectImx, bool TryToRelogin, const FImtblPassportResponseDelegate& ResponseDelegate) { -#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC - if (bIsLoggedIn && IsPKCEConnected) + SetStateFlags(IPS_CONNECTING); + if (IsConnectImx) { - PKCELogoutResponseDelegate = ResponseDelegate; + SetStateFlags(IPS_IMX); + } + if (TryToRelogin) + { + CallJS(IsConnectImx ? ImmutablePassportAction::RECONNECT : ImmutablePassportAction::RELOGIN, TEXT(""), ResponseDelegate, + FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::ReinstateConnection)); + } + else + { + CallJS(ImmutablePassportAction::INIT_DEVICE_FLOW, TEXT(""), ResponseDelegate, + FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnInitDeviceFlowResponse)); } -#endif - CallJS(ImmutablePassportAction::Logout, TEXT(""), ResponseDelegate, - FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnLogoutResponse)); } -void UImmutablePassport::Connect(const FImtblPassportResponseDelegate& ResponseDelegate) +#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC +void UImmutablePassport::ConnectPKCE(bool IsConnectImx, const FImtblPassportResponseDelegate &ResponseDelegate) { - CallJS(ImmutablePassportAction::Connect, TEXT(""), ResponseDelegate, - FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConnectResponse)); + SetStateFlags(IPS_CONNECTING | IPS_PKCE); + if (IsConnectImx) + { + SetStateFlags(IPS_IMX); + } + PKCEResponseDelegate = ResponseDelegate; + CallJS(ImmutablePassportAction::GetPKCEAuthUrl, TEXT(""), PKCEResponseDelegate, + FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnGetPKCEAuthUrlResponse)); } +#endif -void UImmutablePassport::ConnectSilent(const FImtblPassportResponseDelegate& ResponseDelegate) +void UImmutablePassport::Logout(const FImtblPassportResponseDelegate& ResponseDelegate) { - CallJS(ImmutablePassportAction::ConnectSilent, TEXT(""), ResponseDelegate, - FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConnectSilentResponse)); +#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC + if (IsStateFlagSet(IPS_PKCE)) + { + PKCELogoutResponseDelegate = ResponseDelegate; + } +#endif + if (IsStateFlagSet(IPS_CONNECTED)) + { + CallJS(ImmutablePassportAction::Logout, TEXT(""), ResponseDelegate, + FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnLogoutResponse)); + } + else + { + IMTBL_ERR("Passport is not connected to execute logout."); + } } void UImmutablePassport::ConnectEvm(const FImtblPassportResponseDelegate& ResponseDelegate) @@ -200,28 +227,12 @@ void UImmutablePassport::ZkEvmSendTransaction(const FImtblTransactionRequest& Re void UImmutablePassport::ConfirmCode(const FString& DeviceCode, const float Interval, const FImtblPassportResponseDelegate& ResponseDelegate) { - FImmutablePassportCodeConfirmRequestData Data; + FImmutablePassportCodeConfirmRequestData Data { DeviceCode, Interval }; + FString Action = IsStateFlagSet(IPS_IMX) ? ImmutablePassportAction::CONNECT_CONFIRM_CODE : ImmutablePassportAction::LOGIN_CONFIRM_CODE; - Data.deviceCode = DeviceCode; - Data.interval = Interval; - CallJS(ImmutablePassportAction::ConfirmCode, UStructToJsonString(Data), ResponseDelegate, - FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConfirmCodeResponse)); + CallJS(Action, UStructToJsonString(Data), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConfirmCodeResponse)); } -#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC -void UImmutablePassport::ConnectPKCE(const FImtblPassportResponseDelegate &ResponseDelegate) -{ -#if PLATFORM_ANDROID - completingPKCE = false; -#endif - - PKCEResponseDelegate = ResponseDelegate; - CallJS(ImmutablePassportAction::GetPKCEAuthUrl, TEXT(""), PKCEResponseDelegate, - FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnGetPKCEAuthUrlResponse)); -} -#endif - - void UImmutablePassport::GetIdToken(const FImtblPassportResponseDelegate& ResponseDelegate) { CallJS(ImmutablePassportAction::GetIdToken, TEXT(""), ResponseDelegate, @@ -336,15 +347,39 @@ void UImmutablePassport::Setup(const TWeakObjectPtr Connector JSConnector = Connector.Get(); } -bool UImmutablePassport::CheckIsInitialized(const FString& Action, - const FImtblPassportResponseDelegate& ResponseDelegate) const +void UImmutablePassport::ReinstateConnection(FImtblJSResponse Response) { - if (!bIsInitialized) + ResetStateFlags(IPS_CONNECTING); + + if (auto ResponseDelegate = GetResponseDelegate(Response)) + { + // currently, this response has to be called only for RELOGIN AND RECONNECT bridge routines + const FString CallbackName = (Response.responseFor.Compare(ImmutablePassportAction::RELOGIN, ESearchCase::IgnoreCase) == 0) ? "Relogin" : "Reconnect"; + + if (Response.JsonObject->GetBoolField(TEXT("result"))) + { + SetStateFlags(IPS_CONNECTED); + ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ true, "", Response }); + } + else + { + CallJS(ImmutablePassportAction::INIT_DEVICE_FLOW, TEXT(""), ResponseDelegate.GetValue(), + FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnInitDeviceFlowResponse)); + } + } +} + +bool UImmutablePassport::CheckIsInitialized(const FString& Action, const FImtblPassportResponseDelegate& ResponseDelegate) const +{ + const bool IsInitialized = IsStateFlagSet(IPS_INITIALIZED); + + if (!IsInitialized) { IMTBL_WARN("Attempting action '%s' before Passport is initialized", *Action) ResponseDelegate.ExecuteIfBound(FImmutablePassportResult{false, "Passport is not initialized"}); } - return bIsInitialized; + + return IsInitialized; } void UImmutablePassport::CallJS(const FString& Action, const FString& Data, @@ -378,7 +413,7 @@ void UImmutablePassport::OnInitializeResponse(FImtblJSResponse Response) FString Msg; if (Response.success) { - bIsInitialized = true; + SetStateFlags(IPS_INITIALIZED); IMTBL_LOG("Passport initialization succeeded.") } else @@ -393,6 +428,37 @@ void UImmutablePassport::OnInitializeResponse(FImtblJSResponse Response) } } +void UImmutablePassport::OnInitDeviceFlowResponse(FImtblJSResponse Response) +{ + if (auto ResponseDelegate = GetResponseDelegate(Response)) + { + const auto InitDeviceFlowData = JsonObjectToUStruct(Response.JsonObject); + + if (!Response.success || !InitDeviceFlowData || !InitDeviceFlowData->code.Len()) + { + FString Msg; + + IMTBL_WARN("Login device flow initialization attempt failed."); + Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error")); + ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ false, Msg, Response }); + + return; + } + FString Err; + + FPlatformProcess::LaunchURL(*InitDeviceFlowData->url, nullptr, &Err); + if (Err.Len()) + { + FString Msg = "Failed to connect to Browser: " + Err; + + IMTBL_ERR("%s", *Msg); + ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ false, Msg, Response }); + return; + } + ConfirmCode(InitDeviceFlowData->deviceCode, InitDeviceFlowData->interval, ResponseDelegate.GetValue()); + } +} + void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response) { if (auto ResponseDelegate = GetResponseDelegate(Response)) @@ -406,7 +472,7 @@ void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response) if (!Url.IsEmpty()) { #if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC - if (IsPKCEConnected) + if (IsStateFlagSet(IPS_PKCE)) { OnHandleDeepLink = FImtblPassportHandleDeepLinkDelegate::CreateUObject(this, &UImmutablePassport::OnDeepLinkActivated); #if PLATFORM_ANDROID @@ -438,7 +504,7 @@ void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response) { ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ false, "Logout Url is empty", Response }); } - bIsLoggedIn = false; + ResetStateFlags(IPS_CONNECTED); } else { @@ -451,32 +517,6 @@ void UImmutablePassport::OnLogoutResponse(FImtblJSResponse Response) } } -void UImmutablePassport::OnConnectResponse(FImtblJSResponse Response) -{ - if (auto ResponseDelegate = GetResponseDelegate(Response)) - { - const auto ConnectData = JsonObjectToUStruct(Response.JsonObject); - if (!Response.success || !ConnectData || !ConnectData->code.Len()) - { - FString Msg; - IMTBL_WARN("Connect attempt failed."); - Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error")); - ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ false, Msg, Response }); - return; - } - FString Err; - FPlatformProcess::LaunchURL(*ConnectData->url, nullptr, &Err); - if (Err.Len()) - { - FString Msg = "Failed to connect to Browser: " + Err; - IMTBL_ERR("%s", *Msg); - ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ false, Msg, Response }); - return; - } - ConfirmCode(ConnectData->deviceCode, ConnectData->interval, ResponseDelegate.GetValue()); - } -} - void UImmutablePassport::OnConnectSilentResponse(FImtblJSResponse Response) { if (auto ResponseDelegate = GetResponseDelegate(Response)) @@ -560,12 +600,12 @@ void UImmutablePassport::OnConnectPKCEResponse(FImtblJSResponse Response) if (Response.success) { IMTBL_LOG("Successfully connected via PKCE") - bIsLoggedIn = true; - IsPKCEConnected = true; + SetStateFlags(IPS_CONNECTED); } else { IMTBL_WARN("Connect PKCE attempt failed."); + ResetStateFlags(IPS_PKCE); Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error")); } PKCEResponseDelegate.ExecuteIfBound(FImmutablePassportResult{Response.success, Msg}); @@ -575,9 +615,6 @@ void UImmutablePassport::OnConnectPKCEResponse(FImtblJSResponse Response) { IMTBL_ERR("Unable to return a response for Connect PKCE"); } -#if PLATFORM_ANDROID - completingPKCE = false; -#endif } #endif @@ -637,10 +674,9 @@ void UImmutablePassport::OnGetAddressResponse(FImtblJSResponse Response) { if (auto ResponseDelegate = GetResponseDelegate(Response)) { - auto ConnectData = JsonObjectToUStruct(Response.JsonObject); - FString Msg; bool bSuccess = true; + if (!Response.success || !Response.JsonObject->HasTypedField(TEXT("result"))) { IMTBL_WARN("Could not fetch address from Passport."); @@ -738,15 +774,17 @@ void UImmutablePassport::OnConfirmCodeResponse(FImtblJSResponse Response) if (auto ResponseDelegate = GetResponseDelegate(Response)) { FString Msg; - + FString TypeOfConnection = IsStateFlagSet(IPS_IMX) ? TEXT("connect") : TEXT("login"); + + ResetStateFlags(IPS_CONNECTING); if (Response.success) { - IMTBL_LOG("Log in code confirmed.") - bIsLoggedIn = true; + IMTBL_LOG("Code confirmed for %s operation.", *TypeOfConnection) + SetStateFlags(IPS_CONNECTED); } else { - IMTBL_WARN("Login code not confirmed.") + IMTBL_LOG("%s code not confirmed.", *TypeOfConnection) Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error")); } ResponseDelegate->ExecuteIfBound(FImmutablePassportResult{ Response.success, Msg, Response }); @@ -757,10 +795,9 @@ void UImmutablePassport::OnGetEmailResponse(FImtblJSResponse Response) { if (auto ResponseDelegate = GetResponseDelegate(Response)) { - auto ConnectData = JsonObjectToUStruct(Response.JsonObject); - FString Msg; bool bSuccess = true; + if (!Response.success || !Response.JsonObject->HasTypedField(TEXT("result"))) { IMTBL_WARN("Connect attempt failed."); @@ -879,6 +916,20 @@ void UImmutablePassport::LogAndIgnoreResponse(FImtblJSResponse Response) } } +void UImmutablePassport::SetStateFlags(uint8 StateIn) +{ + StateFlags |= StateIn; +} + +void UImmutablePassport::ResetStateFlags(uint8 StateIn) +{ + StateFlags &= ~StateIn; +} + +bool UImmutablePassport::IsStateFlagSet(uint8 StateIn) const +{ + return (StateFlags & StateIn) == StateIn; +} #if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC void UImmutablePassport::OnDeepLinkActivated(FString DeepLink) @@ -896,8 +947,8 @@ void UImmutablePassport::OnDeepLinkActivated(FString DeepLink) PKCELogoutResponseDelegate = nullptr; }, TStatId(), nullptr, ENamedThreads::GameThread); } - - IsPKCEConnected = false; + + ResetStateFlags(IPS_CONNECTED|IPS_PKCE|IPS_IMX); } else if (DeepLink.StartsWith(InitData.redirectUri)) { @@ -907,10 +958,7 @@ void UImmutablePassport::OnDeepLinkActivated(FString DeepLink) void UImmutablePassport::CompleteLoginPKCEFlow(FString Url) { -#if PLATFORM_ANDROID - completingPKCE = true; -#endif - + SetStateFlags(IPS_CONNECTED); // Get code and state from deeplink URL TOptional Code, State; FString Endpoint, Params; @@ -941,18 +989,17 @@ void UImmutablePassport::CompleteLoginPKCEFlow(FString Url) IMTBL_ERR("%s", *ErrorMsg); PKCEResponseDelegate.ExecuteIfBound(FImmutablePassportResult{false, ErrorMsg}); PKCEResponseDelegate = nullptr; -#if PLATFORM_ANDROID - completingPKCE = false; -#endif + ResetStateFlags(IPS_PKCE|IPS_CONNECTING); } else { FImmutablePassportConnectPKCEData Data = FImmutablePassportConnectPKCEData{Code.GetValue(), State.GetValue()}; - - CallJS(ImmutablePassportAction::ConnectPKCE, UStructToJsonString(Data), PKCEResponseDelegate, - FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConnectPKCEResponse)); + + CallJS(IsStateFlagSet(IPS_IMX)? ImmutablePassportAction::CONNECT_PKCE : ImmutablePassportAction::LOGIN_PKCE, + UStructToJsonString(Data), PKCEResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnConnectPKCEResponse)); } } + #endif #if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC @@ -981,7 +1028,7 @@ void UImmutablePassport::HandleOnLoginPKCEDismissed() IMTBL_LOG("Handle On Login PKCE Dismissed"); OnPKCEDismissed = nullptr; - if (!completingPKCE && !bIsLoggedIn) + if (IsStateFlagSet(IPS_CONNECTING)) { // User hasn't entered all required details (e.g. email address) into // Passport yet diff --git a/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp b/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp index 4c1f5752..7e6bde31 100644 --- a/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp +++ b/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp @@ -164,9 +164,11 @@ void UImtblBrowserWidget::HandleOnLoadCompleted() { FString indexUrl = "file:///index.html"; #endif +#if USING_BUNDLED_CEF if (WebBrowserWidget->GetUrl() == indexUrl) { JSConnector->SetMobileBridgeReady(); } +#endif } #endif diff --git a/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h b/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h new file mode 100644 index 00000000..d0bb5e71 --- /dev/null +++ b/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h @@ -0,0 +1,82 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Immutable/ImmutablePassport.h" +#include "ImtblBlueprintAsyncAction.h" +#include "ImtblConnectImxAsyncAction.generated.h" + +/** + * + */ +UCLASS() +class IMMUTABLE_API UImtblConnectionAsyncActions : public UImtblBlueprintAsyncAction +{ + GENERATED_BODY() + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPassportConnectOutputPin, FString, ErrorMessage); + +public: + + /** + * Log into Passport using Device Code Authorisation. + * + * @param WorldContextObject World context + * @param UseCachedSession Whether to use stored credentials for relogin + * + * @return A reference to the object represented by this node + */ + UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable") + static UImtblConnectionAsyncActions* Login(UObject* WorldContextObject, bool UseCachedSession = false); + + /** + * Log into Passport using Device Code Authorisation, initialise the gamer's wallet and instantiate the IMX provider. + * + * @param WorldContextObject World context + * @param UseCachedSession Whether to use stored credentials for relogin + * + * @return A reference to the object represented by this node + */ + UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable") + static UImtblConnectionAsyncActions* ConnectImx(UObject* WorldContextObject, bool UseCachedSession = false); + + /** + * Log into Passport using PKCE + * + * @param WorldContextObject World context + * + * @return A reference to the object represented by this node + */ + UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable") + static UImtblConnectionAsyncActions* LoginPKCE(UObject* WorldContextObject); + + /** + * Log into Passport using PKCE, initialise the gamer's wallet and instantiate the IMX provider. + * + * @param WorldContextObject World context + * + * @return A reference to the object represented by this node + */ + UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable") + static UImtblConnectionAsyncActions* ConnectImxPKCE(UObject* WorldContextObject); + + + void Activate() override; + +private: + + FImmutablePassportInitDeviceFlowData InitDeviceFlowData; + + void DoConnect(TWeakObjectPtr JSConnector); + void OnConnect(FImmutablePassportResult Result); + + UPROPERTY(BlueprintAssignable) + FPassportConnectOutputPin Success; + UPROPERTY(BlueprintAssignable) + FPassportConnectOutputPin Failed; + + bool bUseCachedSession = false; + bool bIsConnectImx = false; + bool bIsPKCE = false; +}; diff --git a/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectAsyncAction.h b/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectAsyncAction.h deleted file mode 100644 index e8bdbf3d..00000000 --- a/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectAsyncAction.h +++ /dev/null @@ -1,40 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Immutable/ImmutablePassport.h" -#include "ImtblBlueprintAsyncAction.h" -#include "ImtblPassportConnectAsyncAction.generated.h" - -/** - * - */ -UCLASS() -class IMMUTABLE_API UImtblPassportConnectAsyncAction - : public UImtblBlueprintAsyncAction { - GENERATED_BODY() - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPassportConnectOutputPin, - FString, ErrorMessage); - -public: - UFUNCTION(BlueprintCallable, - meta = (WorldContext = "WorldContextObject", - BlueprintInternalUseOnly = "true"), - Category = "Immutable") - static UImtblPassportConnectAsyncAction *Connect(UObject *WorldContextObject); - - void Activate() override; - -private: - FImmutablePassportConnectData ConnectData; - - void DoConnect(TWeakObjectPtr JSConnector); - void OnConnect(FImmutablePassportResult Result); - - UPROPERTY(BlueprintAssignable) - FPassportConnectOutputPin Success; - UPROPERTY(BlueprintAssignable) - FPassportConnectOutputPin Failed; -}; diff --git a/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.h b/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.h deleted file mode 100644 index dff7ac69..00000000 --- a/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectPKCEAsyncAction.h +++ /dev/null @@ -1,38 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Immutable/ImmutablePassport.h" -#include "ImtblBlueprintAsyncAction.h" -#include "ImtblPassportConnectPKCEAsyncAction.generated.h" - -UCLASS() -class IMMUTABLE_API UImtblPassportConnectPKCEAsyncAction - : public UImtblBlueprintAsyncAction { - GENERATED_BODY() - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPassportConnectPKCEOutputPin, - FString, ErrorMessage); - -public: - UFUNCTION(BlueprintCallable, - meta = (WorldContext = "WorldContextObject", - BlueprintInternalUseOnly = "true"), - Category = "Immutable") - static UImtblPassportConnectPKCEAsyncAction * - ConnectPKCE(UObject *WorldContextObject); - - void Activate() override; - -private: - FImmutablePassportConnectData ConnectData; - - void DoConnectPKCE(TWeakObjectPtr JSConnector); - void OnConnectPKCE(FImmutablePassportResult Result); - - UPROPERTY(BlueprintAssignable) - FPassportConnectPKCEOutputPin Success; - UPROPERTY(BlueprintAssignable) - FPassportConnectPKCEOutputPin Failed; -}; diff --git a/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectSilentAsyncAction.h b/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectSilentAsyncAction.h deleted file mode 100644 index e6eaf0ac..00000000 --- a/Source/Immutable/Public/Immutable/Actions/ImtblPassportConnectSilentAsyncAction.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "ImtblBlueprintAsyncAction.h" - -#include "ImtblPassportConnectSilentAsyncAction.generated.h" - -/** - * - */ -UCLASS() -class IMMUTABLE_API UImtblPassportConnectSilentAsyncAction : public UImtblBlueprintAsyncAction -{ - GENERATED_BODY() - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPassportConnectSilentOutputPin, FString, ErrorMessage); - -public: - - UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable") - static UImtblPassportConnectSilentAsyncAction* ConnectSilent(UObject* WorldContextObject); - - void Activate() override; - -private: - - UPROPERTY(BlueprintAssignable) - FPassportConnectSilentOutputPin OnSuccess; - UPROPERTY(BlueprintAssignable) - FPassportConnectSilentOutputPin OnFailure; - - void DoConnectSilent(TWeakObjectPtr JSGetConnector); - void OnConnectSilentResponse(struct FImmutablePassportResult Result); -}; diff --git a/Source/Immutable/Public/Immutable/ImmutableDataTypes.h b/Source/Immutable/Public/Immutable/ImmutableDataTypes.h new file mode 100644 index 00000000..6434e782 --- /dev/null +++ b/Source/Immutable/Public/Immutable/ImmutableDataTypes.h @@ -0,0 +1,279 @@ +#pragma once + +#include "JsonObjectConverter.h" +#include "Immutable/ImtblJSMessages.h" +#include "Immutable/ImmutableNames.h" + +#include "ImmutableDataTypes.generated.h" + + +USTRUCT() +struct FImmutableEngineVersionData +{ + GENERATED_BODY() + + UPROPERTY() + FString engine = TEXT("unreal"); + + // cannot have spaces + UPROPERTY() + FString engineVersion = FEngineVersion::Current().ToString().Replace(TEXT(" "), TEXT("_")); + + // cannot have spaces + UPROPERTY() + FString platform = FString(FPlatformProperties::IniPlatformName()).Replace(TEXT(" "), TEXT("_")); + + // cannot have spaces + UPROPERTY() + FString platformVersion = FPlatformMisc::GetOSVersion().Replace(TEXT(" "), TEXT("_")); +}; + +USTRUCT() +struct FImmutablePassportInitData +{ + GENERATED_BODY() + + UPROPERTY() + FString clientId; + + UPROPERTY() + FString redirectUri; + + UPROPERTY() + FString logoutRedirectUri; + + UPROPERTY() + FString environment = ImmutablePassportAction::EnvSandbox; + + UPROPERTY() + FImmutableEngineVersionData engineVersion; + + FString ToJsonString() const; +}; + +USTRUCT() +struct FImmutablePassportInitDeviceFlowData +{ + GENERATED_BODY() + + UPROPERTY() + FString code; + UPROPERTY() + FString deviceCode; + UPROPERTY() + FString url; + UPROPERTY() + float interval = 0; + + static TOptional FromJsonString(const FString& JsonObjectString); +}; + +USTRUCT() +struct FImtblUserProfile +{ + GENERATED_BODY() + UPROPERTY() + FString email; + UPROPERTY() + FString nickname; + UPROPERTY() + FString sub; + +}; + +USTRUCT() +struct FImmutablePassportZkEvmRequestAccountsData +{ + GENERATED_BODY() + + UPROPERTY() + TArray accounts; + + FString ToJsonString() const; + static TOptional FromJsonString(const FString& JsonObjectString); + static TOptional FromJsonObject( + const TSharedPtr& JsonObject); +}; + +USTRUCT() +struct FImmutablePassportZkEvmGetBalanceData +{ + GENERATED_BODY() + + UPROPERTY() + FString address; + + UPROPERTY() + FString blockNumberOrTag = "latest"; + + FString ToJsonString() const; +}; + + +/** + * Key Value wrappers for converting to JSON + */ +USTRUCT() +struct FStringCustomData +{ + GENERATED_BODY() + + UPROPERTY() + FString key; + + UPROPERTY() + FString value; +}; + +USTRUCT() +struct FInt64CustomData +{ + GENERATED_BODY() + + UPROPERTY() + FString key; + + UPROPERTY() + int64 value; +}; + +USTRUCT() +struct FFloatCustomData +{ + GENERATED_BODY() + + UPROPERTY() + FString key; + + UPROPERTY() + float value; +}; + +USTRUCT() +struct FBoolCustomData +{ + GENERATED_BODY() + + UPROPERTY() + FString key; + + UPROPERTY() + bool value; +}; + +UENUM(BlueprintType) +enum EImtblCustomDataType { String, Int64, Float, Bool }; + +/** + * Blueprint doesn't support any sort of generics or polymorphism. To workaround + * this select the primitive type for this custom data item and set the + * corresponding value. This will later be mapped to the proper API structure. + */ +USTRUCT(BlueprintType) +struct FImtblCustomData +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FString key; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FString stringValue; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + int64 intValue; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + float floatValue; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + bool boolValue; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + TEnumAsByte type; + + /** + * Convert from blueprint structure to the expected API data structure + */ + TSharedPtr ToJsonObject() const + { + switch (type) + { + case String: return FJsonObjectConverter::UStructToJsonObject({key, stringValue}); + case Int64: return FJsonObjectConverter::UStructToJsonObject({key, intValue}); + case Float: return FJsonObjectConverter::UStructToJsonObject({key, floatValue}); + case Bool: return FJsonObjectConverter::UStructToJsonObject({key, boolValue}); + default: + { + } + } + return {}; + } +}; + +USTRUCT() +struct FImmutablePassportCodeConfirmRequestData +{ + GENERATED_BODY() + + UPROPERTY() + FString deviceCode; + UPROPERTY() + float interval = 5; + UPROPERTY() + float timeoutMs = 15 * 60 * 1000; +}; + +USTRUCT() +struct FImmutablePassportConnectPKCEData +{ + GENERATED_BODY() + + UPROPERTY() + FString authorizationCode; + + UPROPERTY() + FString state; +}; + + +USTRUCT() +struct FImmutablePassportResult +{ + GENERATED_BODY() + + UPROPERTY() + bool Success = false; + UPROPERTY() + FString Message; + + FImtblJSResponse Response; +}; + + +USTRUCT(BlueprintType) +struct FImtblAccessListItem +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FString address; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + TArray storageKeys; +}; + +USTRUCT(BlueprintType) +struct FNftTransferDetails +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite) + FString receiver; + + UPROPERTY(BlueprintReadWrite) + FString tokenId; + + UPROPERTY(BlueprintReadWrite) + FString tokenAddress; +}; diff --git a/Source/Immutable/Public/Immutable/ImmutableNames.h b/Source/Immutable/Public/Immutable/ImmutableNames.h new file mode 100644 index 00000000..69a9fce8 --- /dev/null +++ b/Source/Immutable/Public/Immutable/ImmutableNames.h @@ -0,0 +1,34 @@ +#pragma once + +namespace ImmutablePassportAction +{ + const FString INIT = TEXT("init"); + const FString INIT_DEVICE_FLOW = TEXT("initDeviceFlow"); + const FString RELOGIN = TEXT("relogin"); + const FString LOGIN_CONFIRM_CODE = TEXT("loginConfirmCode"); + const FString CONNECT_CONFIRM_CODE = TEXT("connectConfirmCode"); + const FString RECONNECT = TEXT("reconnect"); + const FString Logout = TEXT("logout"); + const FString Connect = TEXT("connect"); + const FString ConnectEvm = TEXT("connectEvm"); + const FString ZkEvmRequestAccounts = TEXT("zkEvmRequestAccounts"); + const FString ZkEvmGetBalance = TEXT("zkEvmGetBalance"); + const FString ZkEvmSendTransaction = TEXT("zkEvmSendTransaction"); + +#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC + const FString GetPKCEAuthUrl = TEXT("getPKCEAuthUrl"); + const FString LOGIN_PKCE = TEXT("loginPKCE");; //+ + const FString CONNECT_PKCE = TEXT("connectPKCE"); +#endif + + const FString GetAddress = TEXT("getAddress"); + const FString GetEmail = TEXT("getEmail"); + const FString GetAccessToken = TEXT("getAccessToken"); + const FString GetIdToken = TEXT("getIdToken"); + const FString ImxTransfer = TEXT("imxTransfer"); + const FString ImxBatchNftTransfer = TEXT("imxBatchNftTransfer"); + const FString EnvSandbox = TEXT("sandbox"); + const FString EnvProduction = TEXT("production"); + const FString ImxIsRegisteredOffchain = TEXT("isRegisteredOffchain"); + const FString ImxRegisterOffchain = TEXT("registerOffchain"); +} // namespace ImmutablePassportAction \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/ImmutablePassport.h b/Source/Immutable/Public/Immutable/ImmutablePassport.h index d7101cd6..2836f2a0 100644 --- a/Source/Immutable/Public/Immutable/ImmutablePassport.h +++ b/Source/Immutable/Public/Immutable/ImmutablePassport.h @@ -9,37 +9,11 @@ #include "Runtime/Core/Public/HAL/Platform.h" #include "UObject/Object.h" -#include "ImmutablePassport.generated.h" - +#include "Immutable/ImmutableDataTypes.h" +#include "Immutable/ImmutableRequests.h" -struct FImtblJSResponse; +#include "ImmutablePassport.generated.h" -namespace ImmutablePassportAction -{ - const FString Initialize = TEXT("init"); - const FString Logout = TEXT("logout"); - const FString Connect = TEXT("connect"); - const FString ConnectSilent = TEXT("reconnect"); - const FString ConnectEvm = TEXT("connectEvm"); - const FString ZkEvmRequestAccounts = TEXT("zkEvmRequestAccounts"); - const FString ZkEvmGetBalance = TEXT("zkEvmGetBalance"); - const FString ZkEvmSendTransaction = TEXT("zkEvmSendTransaction"); - const FString ConfirmCode = TEXT("confirmCode"); -#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC - const FString GetPKCEAuthUrl = TEXT("getPKCEAuthUrl"); - const FString ConnectPKCE = TEXT("connectPKCE"); -#endif - const FString GetAddress = TEXT("getAddress"); - const FString GetEmail = TEXT("getEmail"); - const FString GetAccessToken = TEXT("getAccessToken"); - const FString GetIdToken = TEXT("getIdToken"); - const FString ImxTransfer = TEXT("imxTransfer"); - const FString ImxBatchNftTransfer = TEXT("imxBatchNftTransfer"); - const FString EnvSandbox = TEXT("sandbox"); - const FString EnvProduction = TEXT("production"); - const FString ImxIsRegisteredOffchain = TEXT("isRegisteredOffchain"); - const FString ImxRegisterOffchain = TEXT("registerOffchain"); -} // namespace ImmutablePassportAction template FString UStructToJsonString(const UStructType& InStruct) { @@ -65,348 +39,6 @@ template TOptional JsonObjectToUStruct(const return StructInstance; } -USTRUCT() -struct FImmutablePassportResult -{ - GENERATED_BODY() - - UPROPERTY() - bool Success = false; - UPROPERTY() - FString Message; - - FImtblJSResponse Response; -}; - -USTRUCT() -struct FImmutableEngineVersionData -{ - GENERATED_BODY() - - UPROPERTY() - FString engine = TEXT("unreal"); - - // cannot have spaces - UPROPERTY() - FString engineVersion = FEngineVersion::Current().ToString().Replace(TEXT(" "), TEXT("_")); - - // cannot have spaces - UPROPERTY() - FString platform = FString(FPlatformProperties::IniPlatformName()).Replace(TEXT(" "), TEXT("_")); - - // cannot have spaces - UPROPERTY() - FString platformVersion = FPlatformMisc::GetOSVersion().Replace(TEXT(" "), TEXT("_")); -}; - -USTRUCT() -struct FImmutablePassportInitData -{ - GENERATED_BODY() - - UPROPERTY() - FString clientId; - - UPROPERTY() - FString redirectUri; - - UPROPERTY() - FString logoutRedirectUri; - - UPROPERTY() - FString environment = ImmutablePassportAction::EnvSandbox; - - UPROPERTY() - FImmutableEngineVersionData engineVersion; - - FString ToJsonString() const; -}; - -USTRUCT() -struct FImmutablePassportConnectData -{ - GENERATED_BODY() - - UPROPERTY() - FString code; - UPROPERTY() - FString deviceCode; - UPROPERTY() - FString url; - UPROPERTY() - float interval = 0; - - static TOptional FromJsonString(const FString& JsonObjectString); -}; - -USTRUCT() -struct FImmutablePassportZkEvmRequestAccountsData -{ - GENERATED_BODY() - - UPROPERTY() - TArray accounts; - - FString ToJsonString() const; - static TOptional FromJsonString(const FString& JsonObjectString); - static TOptional FromJsonObject( - const TSharedPtr& JsonObject); -}; - -USTRUCT() -struct FImmutablePassportZkEvmGetBalanceData -{ - GENERATED_BODY() - - UPROPERTY() - FString address; - - UPROPERTY() - FString blockNumberOrTag = "latest"; - - FString ToJsonString() const; -}; - -USTRUCT(BlueprintType) -struct FImtblAccessListItem -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - FString address; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - TArray storageKeys; -}; - -/** - * Key Value wrappers for converting to JSON - */ -USTRUCT() -struct FStringCustomData -{ - GENERATED_BODY() - - UPROPERTY() - FString key; - - UPROPERTY() - FString value; -}; - -USTRUCT() -struct FInt64CustomData -{ - GENERATED_BODY() - - UPROPERTY() - FString key; - - UPROPERTY() - int64 value; -}; - -USTRUCT() -struct FFloatCustomData -{ - GENERATED_BODY() - - UPROPERTY() - FString key; - - UPROPERTY() - float value; -}; - -USTRUCT() -struct FBoolCustomData -{ - GENERATED_BODY() - - UPROPERTY() - FString key; - - UPROPERTY() - bool value; -}; - -UENUM(BlueprintType) -enum EImtblCustomDataType { String, Int64, Float, Bool }; - -/** - * Blueprint doesn't support any sort of generics or polymorphism. To workaround - * this select the primitive type for this custom data item and set the - * corresponding value. This will later be mapped to the proper API structure. - */ -USTRUCT(BlueprintType) -struct FImtblCustomData -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - FString key; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - FString stringValue; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - int64 intValue; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - float floatValue; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - bool boolValue; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - TEnumAsByte type; - - /** - * Convert from blueprint structure to the expected API data structure - */ - TSharedPtr ToJsonObject() const - { - switch (type) - { - case String: return FJsonObjectConverter::UStructToJsonObject({key, stringValue}); - case Int64: return FJsonObjectConverter::UStructToJsonObject({key, intValue}); - case Float: return FJsonObjectConverter::UStructToJsonObject({key, floatValue}); - case Bool: return FJsonObjectConverter::UStructToJsonObject({key, boolValue}); - default: - { - } - } - return {}; - } -}; - -USTRUCT(BlueprintType) -struct FImtblTransactionRequest -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - FString to; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - FString data = "0x"; - - UPROPERTY(BlueprintReadWrite, EditAnywhere) - FString value; -}; - -USTRUCT() -struct FImmutablePassportCodeConfirmRequestData -{ - GENERATED_BODY() - - UPROPERTY() - FString deviceCode; - UPROPERTY() - float interval = 5; - UPROPERTY() - float timeoutMs = 15 * 60 * 1000; -}; - -USTRUCT() -struct FImxTransferRequest -{ - GENERATED_BODY() - - UPROPERTY() - FString receiver; - - UPROPERTY() - FString type; - - UPROPERTY() - FString amount; - - UPROPERTY() - FString tokenId; - - UPROPERTY() - FString tokenAddress; - - FString ToJsonString() const; -}; - -USTRUCT() -struct FImxTransferResponse -{ - GENERATED_BODY() - - UPROPERTY() - FString sentSignature; - - UPROPERTY() - FString status; - - UPROPERTY() - float time; - - UPROPERTY() - unsigned transferId; -}; - -USTRUCT(BlueprintType) -struct FNftTransferDetails -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite) - FString receiver; - - UPROPERTY(BlueprintReadWrite) - FString tokenId; - - UPROPERTY(BlueprintReadWrite) - FString tokenAddress; -}; - -USTRUCT() -struct FImxBatchNftTransferRequest -{ - GENERATED_BODY() - - UPROPERTY() - TArray nftTransferDetails; - - FString ToJsonString() const; -}; - -USTRUCT() -struct FImxBatchNftTransferResponse -{ - GENERATED_BODY() - - UPROPERTY() - TArray transferIds; -}; - -USTRUCT() -struct FImmutablePassportConnectPKCEData -{ - GENERATED_BODY() - - UPROPERTY() - FString authorizationCode; - - UPROPERTY() - FString state; -}; - - -USTRUCT() -struct FImxRegisterOffchainResponse -{ - GENERATED_BODY() - - UPROPERTY() - FString tx_hash; -}; - - /** * */ @@ -429,14 +61,14 @@ class IMMUTABLE_API UImmutablePassport : public UObject #endif void Initialize(const FImmutablePassportInitData& InitData, const FImtblPassportResponseDelegate& ResponseDelegate); - void Logout(const FImtblPassportResponseDelegate& ResponseDelegate); - void Connect(const FImtblPassportResponseDelegate& ResponseDelegate); - void ConnectSilent(const FImtblPassportResponseDelegate& ResponseDelegate); + void Connect(bool IsConnectImx, bool TryToRelogin, const FImtblPassportResponseDelegate& ResponseDelegate); #if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC - void ConnectPKCE(const FImtblPassportResponseDelegate& ResponseDelegate); + void ConnectPKCE(bool IsConnectImx, const FImtblPassportResponseDelegate& ResponseDelegate); #endif + void Logout(const FImtblPassportResponseDelegate& ResponseDelegate); + /** * Initializes the zkEVM provider. * @param ResponseDelegate The response delegate of type @@ -519,18 +151,14 @@ class IMMUTABLE_API UImmutablePassport : public UObject void HasStoredCredentials(const FImtblPassportResponseDelegate& ResponseDelegate); protected: + void Setup(TWeakObjectPtr Connector); - -private: - bool bIsInitialized = false; - bool bIsLoggedIn = false; + void ReinstateConnection(FImtblJSResponse Response); #if PLATFORM_ANDROID DECLARE_DELEGATE(FImtblPassportOnPKCEDismissedDelegate); FImtblPassportOnPKCEDismissedDelegate OnPKCEDismissed; - - bool completingPKCE = false; // Used for the PKCE callback #endif TWeakObjectPtr JSConnector; @@ -545,7 +173,7 @@ class IMMUTABLE_API UImmutablePassport : public UObject // response delegate here so it's easier to get FImtblPassportResponseDelegate PKCEResponseDelegate; FImtblPassportResponseDelegate PKCELogoutResponseDelegate; - bool IsPKCEConnected = false; + // bool IsPKCEConnected = false; #endif // Ensures that Passport has been initialized before calling JS @@ -561,6 +189,9 @@ class IMMUTABLE_API UImmutablePassport : public UObject const FImtblPassportResponseDelegate& ResponseDelegate); void OnInitializeResponse(FImtblJSResponse Response); + + void OnInitDeviceFlowResponse(FImtblJSResponse Response); + void OnLogoutResponse(FImtblJSResponse Response); void OnConnectResponse(FImtblJSResponse Response); void OnConnectSilentResponse(FImtblJSResponse Response); @@ -594,4 +225,22 @@ class IMMUTABLE_API UImmutablePassport : public UObject void CallJniStaticVoidMethod(JNIEnv* Env, const jclass Class, jmethodID Method, ...); void LaunchAndroidUrl(FString Url); #endif + + void SetStateFlags(uint8 StateIn); + void ResetStateFlags(uint8 StateIn); + bool IsStateFlagSet(uint8 StateIn) const; + +private: + + enum EImmutablePassportStateFlags : uint8 + { + IPS_NONE = 0, + IPS_CONNECTING = 1 << 0, + IPS_CONNECTED = 1 << 1, + IPS_IMX = 1 << 2, + IPS_PKCE = 1 << 3, + IPS_INITIALIZED = 1 << 4 + }; + + uint8 StateFlags = IPS_NONE; }; diff --git a/Source/Immutable/Public/Immutable/ImmutableRequests.h b/Source/Immutable/Public/Immutable/ImmutableRequests.h new file mode 100644 index 00000000..ce912fe0 --- /dev/null +++ b/Source/Immutable/Public/Immutable/ImmutableRequests.h @@ -0,0 +1,54 @@ +#pragma once + + +#include "ImmutableRequests.generated.h" + + +USTRUCT(BlueprintType) +struct FImtblTransactionRequest +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FString to; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FString data = "0x"; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + FString value; +}; + +USTRUCT() +struct FImxTransferRequest +{ + GENERATED_BODY() + + UPROPERTY() + FString receiver; + + UPROPERTY() + FString type; + + UPROPERTY() + FString amount; + + UPROPERTY() + FString tokenId; + + UPROPERTY() + FString tokenAddress; + + FString ToJsonString() const; +}; + +USTRUCT() +struct FImxBatchNftTransferRequest +{ + GENERATED_BODY() + + UPROPERTY() + TArray nftTransferDetails; + + FString ToJsonString() const; +}; \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/ImmutableResponses.h b/Source/Immutable/Public/Immutable/ImmutableResponses.h new file mode 100644 index 00000000..5007bcbd --- /dev/null +++ b/Source/Immutable/Public/Immutable/ImmutableResponses.h @@ -0,0 +1,40 @@ +#pragma once + +#include "ImmutableResponses.generated.h" + + +USTRUCT() +struct FImxTransferResponse +{ + GENERATED_BODY() + + UPROPERTY() + FString sentSignature; + + UPROPERTY() + FString status; + + UPROPERTY() + float time; + + UPROPERTY() + unsigned transferId; +}; + +USTRUCT() +struct FImxBatchNftTransferResponse +{ + GENERATED_BODY() + + UPROPERTY() + TArray transferIds; +}; + +USTRUCT() +struct FImxRegisterOffchainResponse +{ + GENERATED_BODY() + + UPROPERTY() + FString tx_hash; +}; \ No newline at end of file