diff --git a/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset b/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset index 65a8d04..5a899f5 100644 Binary files a/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset and b/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset differ diff --git a/Content/BlueprintSampleContent/ImtblUnauthenticatedLevel.umap b/Content/BlueprintSampleContent/ImtblUnauthenticatedLevel.umap index d540e53..c3d9cf3 100644 Binary files a/Content/BlueprintSampleContent/ImtblUnauthenticatedLevel.umap and b/Content/BlueprintSampleContent/ImtblUnauthenticatedLevel.umap differ diff --git a/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset b/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset index e5c5cee..c3435c8 100644 Binary files a/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset and b/Content/BlueprintSampleContent/ImtblUnauthenticatedWidget4_26.uasset differ diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp index e6eb398..2523060 100644 --- a/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp +++ b/Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp @@ -37,7 +37,7 @@ void UImtblConnectionAsyncActions::Activate() { FString Error = "Connect failed due to missing world or world context object."; IMTBL_WARN("%s", *Error) - Failed.Broadcast(Error); + Internal_DynamicMulticastDelegate_OnFailed.Broadcast(Error); return; } @@ -45,6 +45,16 @@ void UImtblConnectionAsyncActions::Activate() GetSubsystem()->WhenReady(this, &UImtblConnectionAsyncActions::DoConnect); } +UImtblConnectionAsyncActions::FPassportConnectOutputPin* UImtblConnectionAsyncActions::DynamicMulticastDelegate_OnSuccess() +{ + return &Internal_DynamicMulticastDelegate_OnSuccess; +} + +UImtblConnectionAsyncActions::FPassportConnectOutputPin* UImtblConnectionAsyncActions::DynamicMulticastDelegate_OnFailed() +{ + return &Internal_DynamicMulticastDelegate_OnFailed; +} + void UImtblConnectionAsyncActions::DoConnect(TWeakObjectPtr JSConnector) { auto Passport = GetSubsystem()->GetPassport(); @@ -68,10 +78,10 @@ void UImtblConnectionAsyncActions::OnConnect(FImmutablePassportResult Result) { if (Result.Success) { - Success.Broadcast(TEXT("")); + Internal_DynamicMulticastDelegate_OnSuccess.Broadcast(Result.ToJsonString()); } else { - Failed.Broadcast(Result.Error); + Internal_DynamicMulticastDelegate_OnFailed.Broadcast(Result.Error); } } diff --git a/Source/Immutable/Private/Immutable/Actions/ImtblEmbeddedLoginAsyncAction.cpp b/Source/Immutable/Private/Immutable/Actions/ImtblEmbeddedLoginAsyncAction.cpp new file mode 100644 index 0000000..86edda0 --- /dev/null +++ b/Source/Immutable/Private/Immutable/Actions/ImtblEmbeddedLoginAsyncAction.cpp @@ -0,0 +1,131 @@ +#include "Immutable/Actions/ImtblEmbeddedLoginAsyncAction.h" + +#include "Immutable/Actions/ImtblConnectImxAsyncAction.h" +#include "Immutable/Browser/ImmutableJSConnectorBrowserWidget.h" + +UImtblEmbeddedLoginAsyncAction* UImtblEmbeddedLoginAsyncAction::Login(UObject* WorldContextObject, UImmutableJSConnectorBrowserWidget* JSConnectorBrowserWidget) +{ + ThisClass* Action = NewObject(); + + if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) + { + Action->BindedWorld = World; + Action->BindedJSConnectorBrowserWidget = JSConnectorBrowserWidget; + Action->RegisterWithGameInstance(WorldContextObject); + } + + return Action; +} + +void UImtblEmbeddedLoginAsyncAction::Activate() +{ + Super::Activate(); + + if (UWorld* World = BindedWorld.Get()) + { + if (UImmutableJSConnectorBrowserWidget* JSConnectorBrowserWidget = BindedJSConnectorBrowserWidget.Get()) + { + JSConnectorBrowserWidget->GetJSConnector()->MulticastDelegate_OnEventToGame()->AddWeakLambda(this, [this, World, JSConnectorBrowserWidget](const FString& Event, const FString& Message, const TOptional& Response) + { + if (Event == TEXT("HandleLoginData") && Response.IsSet()) + { + FImmutableDirectLoginOptions DirectLoginOptions; + if (UImmutableDirectLoginOptionsStatics::FromJSResponse(Response.GetValue(), DirectLoginOptions)) + { + if ((LoginAsyncAction = UImtblConnectionAsyncActions::Login(World, DirectLoginOptions))) + { + LoginAsyncAction->DynamicMulticastDelegate_OnSuccess()->AddDynamic(this, &ThisClass::LoginAsyncAction_DynamicMulticastDelegate_OnSuccess); + LoginAsyncAction->DynamicMulticastDelegate_OnFailed()->AddDynamic(this, &ThisClass::LoginAsyncAction_DynamicMulticastDelegate_OnFailed); + LoginAsyncAction->Activate(); + } + else + { + SetReadyToDestroy(); + } + } + } + else if (Event == TEXT("HandleClose")) + { + JSConnectorBrowserWidget->LoadURL(TEXT("about:blank")); + Internal_Closed_MulticastDelegate.Broadcast(); + Internal_Closed_DynamicMulticastDelegate.Broadcast(); + SetReadyToDestroy(); + } + }); + JSConnectorBrowserWidget->LoadURL(TEXT("https://auth.immutable.com/im-embedded-login-prompt?isWebView=true")); + } + else + { + SetReadyToDestroy(); + } + } + else + { + SetReadyToDestroy(); + } +} + +void UImtblEmbeddedLoginAsyncAction::SetReadyToDestroy() +{ + if (LoginAsyncAction) + { + LoginAsyncAction->DynamicMulticastDelegate_OnSuccess()->RemoveAll(this); + LoginAsyncAction->DynamicMulticastDelegate_OnFailed()->RemoveAll(this); + LoginAsyncAction->SetReadyToDestroy(); + LoginAsyncAction = nullptr; + } + if (UImmutableJSConnectorBrowserWidget* JSConnectorBrowserWidget = BindedJSConnectorBrowserWidget.Get()) + { + JSConnectorBrowserWidget->GetJSConnector()->MulticastDelegate_OnEventToGame()->RemoveAll(this); + } + Super::SetReadyToDestroy(); +} + +UImtblConnectionAsyncActions* UImtblEmbeddedLoginAsyncAction::GetLoginAsyncAction() const +{ + return LoginAsyncAction; +} + +FImmutableMessageMulticastDelegate* UImtblEmbeddedLoginAsyncAction::LoginFailed_MulticastDelegate() +{ + return &Internal_LoginFailed_MulticastDelegate; +} + +FImmutableMessageDynamicMulticastDelegate* UImtblEmbeddedLoginAsyncAction::LoginFailed_DynamicMulticastDelegate() +{ + return &Internal_LoginFailed_DynamicMulticastDelegate; +} + +FSimpleMulticastDelegate* UImtblEmbeddedLoginAsyncAction::LoginSuccess_MulticastDelegate() +{ + return &Internal_LoginSuccess_MulticastDelegate; +} + +FImmutableSimpleDynamicMulticastDelegate* UImtblEmbeddedLoginAsyncAction::LoginSuccess_DynamicMulticastDelegate() +{ + return &Internal_LoginSuccess_DynamicMulticastDelegate; +} + +FSimpleMulticastDelegate* UImtblEmbeddedLoginAsyncAction::Closed_MulticastDelegate() +{ + return &Internal_Closed_MulticastDelegate; +} + +FImmutableSimpleDynamicMulticastDelegate* UImtblEmbeddedLoginAsyncAction::Closed_DynamicMulticastDelegate() +{ + return &Internal_Closed_DynamicMulticastDelegate; +} + +void UImtblEmbeddedLoginAsyncAction::LoginAsyncAction_DynamicMulticastDelegate_OnSuccess(FString String) +{ + Internal_LoginSuccess_MulticastDelegate.Broadcast(); + Internal_LoginSuccess_DynamicMulticastDelegate.Broadcast(); + SetReadyToDestroy(); +} + +void UImtblEmbeddedLoginAsyncAction::LoginAsyncAction_DynamicMulticastDelegate_OnFailed(FString String) +{ + Internal_LoginFailed_MulticastDelegate.Broadcast(String); + Internal_LoginFailed_DynamicMulticastDelegate.Broadcast(String); + SetReadyToDestroy(); +} \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/Browser/ImmutableBaseBrowserWidget.cpp b/Source/Immutable/Private/Immutable/Browser/ImmutableBaseBrowserWidget.cpp new file mode 100644 index 0000000..be73921 --- /dev/null +++ b/Source/Immutable/Private/Immutable/Browser/ImmutableBaseBrowserWidget.cpp @@ -0,0 +1,142 @@ +#include "Immutable/Browser/ImmutableBaseBrowserWidget.h" + +#include "Immutable/Misc/ImtblLogging.h" + +#define LOCTEXT_NAMESPACE "BaseBrowserWidget" + +void UImmutableBaseBrowserWidget::ReleaseSlateResources(bool bReleaseChildren) +{ + Super::ReleaseSlateResources(bReleaseChildren); + +#if USING_BUNDLED_CEF + WebBrowserWidget.Reset(); +#endif +} + +bool UImmutableBaseBrowserWidget::IsPageLoaded() const +{ +#if USING_BUNDLED_CEF + return WebBrowserWidget.IsValid() && WebBrowserWidget->IsLoaded(); +#endif + return false; +} + +void UImmutableBaseBrowserWidget::LoadURL(FString NewURL) const +{ +#if USING_BUNDLED_CEF + if (WebBrowserWidget.IsValid()) + { + return WebBrowserWidget->LoadURL(NewURL); + } +#endif +} + +void UImmutableBaseBrowserWidget::LoadString(FString Contents, FString DummyURL) +{ +#if USING_BUNDLED_CEF + if (WebBrowserWidget.IsValid()) + { + WebBrowserWidget->LoadString(Contents, DummyURL); + } +#endif +} + +FImmutableBrowserConsoleMessageDynamicMulticastDelegate* UImmutableBaseBrowserWidget::DynamicMulticastDelegate_OnConsoleMessage() +{ + return &Internal_DynamicMulticastDelegate_OnConsoleMessage; +} + +FSimpleMulticastDelegate UImmutableBaseBrowserWidget::MulticastDelegate_OnLoadCompleted() +{ + return Internal_MulticastDelegate_OnLoadCompleted; +} + +FSimpleMulticastDelegate* UImmutableBaseBrowserWidget::MulticastDelegate_OnBrowserCreated() +{ + return &Internal_MulticastDelegate_OnBrowserCreated; +} + +TSharedRef UImmutableBaseBrowserWidget::RebuildWidget() +{ + if (IsDesignTime()) + { + // Show placeholder in editor + return SNew(SBox) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("BrowserPlaceholder", "Browser Widget")) + ]; + } + else + { +#if USING_BUNDLED_CEF + // Create the web browser widget + WebBrowserWidget = SNew(SWebBrowser) + .InitialURL(InitialURL) + .ShowControls(false) + .SupportsTransparency(bSupportsTransparency) + .ShowInitialThrobber(bShowInitialThrobber) +#if PLATFORM_ANDROID || PLATFORM_IOS + .OnLoadCompleted(BIND_UOBJECT_DELEGATE(FSimpleDelegate, HandleOnLoadCompleted)) +#endif +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)) +#endif + ; + + return WebBrowserWidget.ToSharedRef(); +#else + // Fallback for non-CEF builds + return + SNew(SBox) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("NoCEF", "Browser Not Available")) + ]; +#endif + } +} + +void UImmutableBaseBrowserWidget::OnWidgetRebuilt() +{ + Super::OnWidgetRebuilt(); + + OnBrowserCreated(); +} + +#if PLATFORM_ANDROID || PLATFORM_IOS +void UImmutableBaseBrowserWidget::HandleOnLoadCompleted() +{ + // Default mobile load handling + HandleLoadCompleted(); +} +#endif + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 +void UImmutableBaseBrowserWidget::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) +{ + UE_LOG(LogImmutable, Log, TEXT("Browser console message: %s, Source: %s, Line: %d, Severity: %d"), *Message, *Source, Line, Severity); + HandleConsoleMessage(Message, Source, Line, static_cast(Severity)); +} +#endif + +void UImmutableBaseBrowserWidget::HandleLoadCompleted() +{ + Internal_MulticastDelegate_OnLoadCompleted.Broadcast(); +} + +void UImmutableBaseBrowserWidget::HandleConsoleMessage(const FString& Message, const FString& Source, int32 Line, int32 Severity) +{ + Internal_DynamicMulticastDelegate_OnConsoleMessage.Broadcast(Message, Source, Line, Severity); +} + +void UImmutableBaseBrowserWidget::OnBrowserCreated() +{ + Internal_MulticastDelegate_OnBrowserCreated.Broadcast(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/Browser/ImmutableJSConnectorBrowserWidget.cpp b/Source/Immutable/Private/Immutable/Browser/ImmutableJSConnectorBrowserWidget.cpp new file mode 100644 index 0000000..23d0ade --- /dev/null +++ b/Source/Immutable/Private/Immutable/Browser/ImmutableJSConnectorBrowserWidget.cpp @@ -0,0 +1,67 @@ +#include "Immutable/Browser/ImmutableJSConnectorBrowserWidget.h" + +#include "Immutable/ImtblJSConnector.h" + +void UImmutableJSConnectorBrowserWidget::PostInitProperties() +{ + Super::PostInitProperties(); + + if (!IsTemplate()) + { + JSConnector = NewObject(this); + JSConnector->ExecuteJs.BindUObject(this, &ThisClass::ExecuteJavaScript); + } +} + +UImtblJSConnector* UImmutableJSConnectorBrowserWidget::GetJSConnector() const +{ + return JSConnector; +} + +void UImmutableJSConnectorBrowserWidget::ExecuteJavaScript(const FString& ScriptText) const +{ +#if USING_BUNDLED_CEF + if (WebBrowserWidget.IsValid()) + { + WebBrowserWidget->ExecuteJavascript(ScriptText); + } +#endif +} + +bool UImmutableJSConnectorBrowserWidget::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent) const +{ +#if USING_BUNDLED_CEF + if (!WebBrowserWidget.IsValid() || !Object) + { + UE_LOG(LogImmutable, Warning, TEXT("Could not bind UObject '%s' to browser, WebBrowserWidget is null"), *Object->GetName()); + return false; + } + + WebBrowserWidget->BindUObject(Name, Object, bIsPermanent); + return true; +#endif + return false; +} + +void UImmutableJSConnectorBrowserWidget::OnBrowserCreated() +{ + Super::OnBrowserCreated(); + + SetupJavaScriptBindings(); +} + +void UImmutableJSConnectorBrowserWidget::SetupJavaScriptBindings() +{ + if (JSConnector && JSConnector->IsBound()) + { + return; + } + + if (JSConnector) + { + if (BindUObject(UImtblJSConnector::JSObjectName(), JSConnector)) + { + JSConnector->Init(IsPageLoaded()); + } + } +} \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/ImmutableDataTypes.cpp b/Source/Immutable/Private/Immutable/ImmutableDataTypes.cpp index 50bdd48..da71132 100644 --- a/Source/Immutable/Private/Immutable/ImmutableDataTypes.cpp +++ b/Source/Immutable/Private/Immutable/ImmutableDataTypes.cpp @@ -2,6 +2,8 @@ #include "Immutable/ImmutableDataTypes.h" +#include "Immutable/Actions/ImtblConnectImxAsyncAction.h" + #if PLATFORM_WINDOWS #include "Immutable/Windows/ImmutablePKCEWindows.h" #endif @@ -83,6 +85,13 @@ FString FImmutablePassportZkEvmGetBalanceData::ToJsonString() const return OutString; } +FString FImmutablePassportResult::ToJsonString() const +{ + FString Result; + FJsonObjectConverter::UStructToJsonObjectString(*this, Result); + return Result; +} + void UImmutablePKCEData::BeginDestroy() { Reset(); @@ -115,4 +124,9 @@ TSharedPtr FImmutableDirectLoginOptions::ToJsonObject() const JsonObject->SetStringField(TEXT("marketingConsentStatus"), StaticEnum()->GetNameStringByValue(static_cast(MarketingConsentStatus)).ToLower()); return JsonObject; +} + +bool UImmutableDirectLoginOptionsStatics::FromJSResponse(const FImtblJSResponse& Response, FImmutableDirectLoginOptions& DirectLoginOptions) +{ + return FJsonObjectConverter::JsonObjectToUStruct(Response.JsonObject.ToSharedRef(), &DirectLoginOptions); } \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp index 4efd7d0..d5511e4 100644 --- a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp +++ b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp @@ -94,7 +94,6 @@ void UImmutablePassport::Connect(bool IsConnectImx, const FImtblPassportResponse #if PLATFORM_WINDOWS // Verify PKCEData is null before initializing to ensure we're not overriding an active PKCE operation. // A non-null value indicates another PKCE operation is already in progress. - ensureAlways(!PKCEData); PKCEData = UImmutablePKCEWindows::Initialise(InitData); if (PKCEData) { @@ -118,6 +117,8 @@ void UImmutablePassport::Connect(bool IsConnectImx, const FImtblPassportResponse RequestObject->SetObjectField(TEXT("directLoginOptions"), DirectLoginOptionsObject); } + RequestObject->SetStringField(TEXT("imPassportTraceId"), DirectLoginOptions.ImPassportTraceId); + // Convert to JSON string FString PKCERequestJson; TSharedRef> Writer = TJsonWriterFactory<>::Create(&PKCERequestJson); @@ -539,7 +540,7 @@ void UImmutablePassport::OnConnectResponse(FImtblJSResponse Response) Response.Error.IsSet() ? Msg = Response.Error->ToString() : Msg = Response.JsonObject->GetStringField(TEXT("error")); } Analytics->Track(IsStateFlagsSet(IPS_IMX) ? UImmutableAnalytics::EEventName::COMPLETE_CONNECT_IMX_PKCE : UImmutableAnalytics::EEventName::COMPLETE_LOGIN_PKCE, Response.success); - PKCEResponseDelegate.ExecuteIfBound(FImmutablePassportResult{Response.success, Msg}); + PKCEResponseDelegate.ExecuteIfBound(FImmutablePassportResult{Response.success, Msg, Response}); PKCEResponseDelegate = nullptr; // we save passport state for PKCE flow in case if we decide to close a game diff --git a/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.cpp b/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.cpp index a5f979a..e5868ee 100644 --- a/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.cpp +++ b/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.cpp @@ -5,12 +5,12 @@ #include "Blueprint/WidgetTree.h" #include "Components/CanvasPanel.h" #include "Components/CanvasPanelSlot.h" -#include "Components/PanelWidget.h" #include "Components/ScaleBox.h" #include "Components/ScaleBoxSlot.h" + +#include "Immutable/Browser/ImmutableJSConnectorBrowserWidget.h" +#include "Immutable/ImmutableUtilities.h" #include "Immutable/Misc/ImtblLogging.h" -#include "ImtblBrowserWidget.h" -#include "Immutable/ImtblJSConnector.h" TSharedRef UImtblBrowserUserWidget::RebuildWidget() { @@ -35,21 +35,47 @@ TSharedRef UImtblBrowserUserWidget::RebuildWidget() RootWidget->AddChild(ScaleBox); if (ScaleBox) { - Browser = WidgetTree->ConstructWidget(UImtblBrowserWidget::StaticClass(), TEXT("ImmutableBrowserWidget")); - ScaleBox->AddChild(Browser); + W_Browser = WidgetTree->ConstructWidget(UImmutableJSConnectorBrowserWidget::StaticClass(), TEXT("GameBridgeWidget")); + W_Browser->MulticastDelegate_OnLoadCompleted().AddWeakLambda(this, []() + { +#if PLATFORM_ANDROID | PLATFORM_IOS + FString IndexURL = "file:///immutable/index.html"; + +#if USING_BUNDLED_CEF + if (WebBrowserWidget->GetUrl() == IndexURL) + { + JSConnector->SetMobileBridgeReady(); + } + else + { + UE_LOG(LogImmutable, Error, TEXT("Immutable Browser Widget Url don't match: (loaded : %s, required: %s)"), *WebBrowserWidget->GetUrl(), *IndexURL); + } +#endif +#endif + }); + W_Browser->MulticastDelegate_OnBrowserCreated()->AddWeakLambda(this, [this]() + { + FString JavaScript; + if (FImmutableUtilities::LoadGameBridge(JavaScript)) + { + FString IndexHtml = FString("GameSDK Bridge

Bridge Running

"); + W_Browser->LoadString(IndexHtml, TEXT("file:///immutable/index.html")); + } + }); + ScaleBox->AddChild(W_Browser); if (UCanvasPanelSlot* RootWidgetSlot = Cast(ScaleBox->Slot)) { #if PLATFORM_ANDROID | PLATFORM_IOS - // Android webview needs to be at least 1px to 1px big to work - // but it can be off screen - RootWidgetSlot->SetAnchors(FAnchors(0, 0, 0, 0)); - RootWidgetSlot->SetOffsets(FMargin(-1, -1, 1, 1)); + // Android webview needs to be at least 1px to 1px big to work + // but it can be off screen + RootWidgetSlot->SetAnchors(FAnchors(0, 0, 0, 0)); + RootWidgetSlot->SetOffsets(FMargin(-1, -1, 1, 1)); #else RootWidgetSlot->SetAnchors(FAnchors(0, 0, 1, 1)); RootWidgetSlot->SetOffsets(DefaultOffsets); #endif } - if (UScaleBoxSlot* ScaleBoxSlot = Cast(Browser->Slot)) + if (UScaleBoxSlot* ScaleBoxSlot = Cast(W_Browser->Slot)) { ScaleBoxSlot->SetHorizontalAlignment(HAlign_Fill); ScaleBoxSlot->SetVerticalAlignment(VAlign_Fill); @@ -85,13 +111,18 @@ void UImtblBrowserUserWidget::OnWidgetRebuilt() Super::OnWidgetRebuilt(); } +UImmutableJSConnectorBrowserWidget* UImtblBrowserUserWidget::GetBrowser() const +{ + return W_Browser; +} + TWeakObjectPtr UImtblBrowserUserWidget::GetJSConnector() const { - if (!Browser) + if (!W_Browser) { IMTBL_WARN("JSConnector requested before Browser was initialized"); return nullptr; } - return Browser->GetJSConnector(); -} + return W_Browser->GetJSConnector(); +} \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.h b/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.h index 6857606..7c83112 100644 --- a/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.h +++ b/Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.h @@ -1,11 +1,11 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once +#pragma once #include "Blueprint/UserWidget.h" -#include "CoreMinimal.h" + #include "ImtblBrowserUserWidget.generated.h" +class UImmutableJSConnectorBrowserWidget; + UCLASS() class IMMUTABLE_API UImtblBrowserUserWidget : public UUserWidget { @@ -17,15 +17,16 @@ class IMMUTABLE_API UImtblBrowserUserWidget : public UUserWidget virtual void RemoveFromParent() override; virtual void OnWidgetRebuilt() override; + UImmutableJSConnectorBrowserWidget* GetBrowser() const; TWeakObjectPtr GetJSConnector() const; private: UPROPERTY() - class UImtblBrowserWidget* Browser = nullptr; + TObjectPtr W_Browser; bool bIsBrowserAppInitialized = false; FTimerHandle Timer; FMargin DefaultOffsets = FMargin(150, 150, 150, 150); -}; +}; \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp b/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp deleted file mode 100644 index 8fe4120..0000000 --- a/Source/Immutable/Private/Immutable/ImtblBrowserWidget.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "ImtblBrowserWidget.h" - -#include "Misc/FileHelper.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Text/STextBlock.h" - -#include "Immutable/Misc/ImtblLogging.h" -#include "Immutable/ImtblJSConnector.h" -#include "Immutable/ImmutableUtilities.h" -#if USING_BUNDLED_CEF -#include "SWebBrowser.h" -#endif - -UImtblBrowserWidget::UImtblBrowserWidget() -{ - IMTBL_LOG_FUNCSIG - - JSConnector = NewObject(this, "JSConnector"); - JSConnector->ExecuteJs = UImtblJSConnector::FOnExecuteJsDelegate::CreateUObject(this, &UImtblBrowserWidget::ExecuteJS); - - InitialURL = TEXT("about:blank"); -} - -void UImtblBrowserWidget::BindConnector() -{ - if (JSConnector && JSConnector->IsBound()) - { - return; - } - - IMTBL_LOG("Setting up %s...", *UImtblJSConnector::StaticClass()->GetName()) - - if (JSConnector) - { - if (BindUObject(UImtblJSConnector::JSObjectName(), JSConnector)) - { - JSConnector->Init(IsPageLoaded()); - } - } -} - -TWeakObjectPtr UImtblBrowserWidget::GetJSConnector() const -{ - return JSConnector; -} - -bool UImtblBrowserWidget::IsPageLoaded() const -{ -#if USING_BUNDLED_CEF - return WebBrowserWidget.IsValid() && WebBrowserWidget->IsLoaded(); -#else - return false; -#endif -} - -void UImtblBrowserWidget::ExecuteJS(const FString& ScriptText) const -{ -#if USING_BUNDLED_CEF - if (WebBrowserWidget.IsValid()) - { - WebBrowserWidget->ExecuteJavascript(ScriptText); - } -#endif -} - -void UImtblBrowserWidget::SetBrowserContent() -{ -#if USING_BUNDLED_CEF - if (!WebBrowserWidget.IsValid()) - { - IMTBL_ERR("Browser widget is not valid") - return; - } - - FString JavaScript; - - if (FImmutableUtilities::LoadGameBridge(JavaScript)) - { - FString IndexHtml = FString("GameSDK Bridge

Bridge Running

"); - - WebBrowserWidget->LoadString(IndexHtml, TEXT("file:///immutable/index.html")); - } -#endif -} - -bool UImtblBrowserWidget::BindUObject(const FString& Name, UObject* Object, const bool bIsPermanent) const -{ -#if USING_BUNDLED_CEF - if (!WebBrowserWidget) - { - IMTBL_WARN_FUNC("Could not bind UObject '%s' to browser, WebBrowserWidget is null", *Object->GetName()) - return false; - } - - WebBrowserWidget->BindUObject(Name, Object, bIsPermanent); -#endif - return true; -} - -void UImtblBrowserWidget::ReleaseSlateResources(bool bReleaseChildren) -{ - Super::ReleaseSlateResources(bReleaseChildren); -#if USING_BUNDLED_CEF - WebBrowserWidget.Reset(); -#endif -} - -TSharedRef UImtblBrowserWidget::RebuildWidget() -{ - if (IsDesignTime()) - { - return SNew(SBox).HAlign(HAlign_Center).VAlign(VAlign_Center)[SNew(STextBlock).Text(NSLOCTEXT("Immutable", "Immutable Web Browser", "Immutable Web Browser"))]; - } - else - { -#if USING_BUNDLED_CEF - WebBrowserWidget = SNew(SWebBrowser).InitialURL(InitialURL).ShowControls(false).SupportsTransparency(bSupportsTransparency).ShowInitialThrobber(bShowInitialThrobber) -#if PLATFORM_ANDROID | PLATFORM_IOS - .OnLoadCompleted( - BIND_UOBJECT_DELEGATE(FSimpleDelegate, HandleOnLoadCompleted)) -#endif -#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 - .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)) -#endif - ; - - return WebBrowserWidget.ToSharedRef(); -#else - return SNew(SBox) - .HAlign(HAlign_Center) - .VAlign(VAlign_Center)[SNew(STextBlock) - .Text(NSLOCTEXT("Immutable", - "Immutable Web Browser", - "Immutable Web Browser"))]; -#endif - } -} - -#if PLATFORM_ANDROID | PLATFORM_IOS -void UImtblBrowserWidget::HandleOnLoadCompleted() -{ - FString indexUrl = "file:///immutable/index.html"; - -#if USING_BUNDLED_CEF - if (WebBrowserWidget->GetUrl() == indexUrl) - { - JSConnector->SetMobileBridgeReady(); - } - else - { - IMTBL_ERR("Immutable Browser Widget Url don't match: (loaded : %s, required: %s)", *WebBrowserWidget->GetUrl(), *indexUrl); - } -#endif -} -#endif - -void UImtblBrowserWidget::OnWidgetRebuilt() -{ - Super::OnWidgetRebuilt(); - BindConnector(); - SetBrowserContent(); -} - -#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 -void UImtblBrowserWidget::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) -{ - // TODO: add severity to log and callback - IMTBL_LOG("Browser console message: %s, Source: %s, Line: %d", *Message, *Source, Line); - OnConsoleMessage.Broadcast(Message, Source, Line); -} -#endif \ No newline at end of file diff --git a/Source/Immutable/Private/Immutable/ImtblBrowserWidget.h b/Source/Immutable/Private/Immutable/ImtblBrowserWidget.h deleted file mode 100644 index b756041..0000000 --- a/Source/Immutable/Private/Immutable/ImtblBrowserWidget.h +++ /dev/null @@ -1,73 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "Runtime/Launch/Resources/Version.h" - -#if USING_BUNDLED_CEF -#include "IWebBrowserWindow.h" -#endif -#include "Components/Widget.h" -#include "ImtblBrowserWidget.generated.h" - -UCLASS() -class IMMUTABLE_API UImtblBrowserWidget : public UWidget -{ - GENERATED_BODY() - - friend class UImtblJSConnector; - -public: - // Sets default values for this actor's properties - UImtblBrowserWidget(); - - // Get a pointer to the JSConnector - TWeakObjectPtr GetJSConnector() const; - - bool IsPageLoaded() const; - -protected: - void ExecuteJS(const FString& ScriptText) const; - -private: - DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnConsoleMessage, const FString &, Message, const FString &, Source, int32, Line); - - FOnConsoleMessage OnConsoleMessage; - -#if USING_BUNDLED_CEF - TSharedPtr WebBrowserWidget; -#endif - /** URL that the browser will initially navigate to. The URL should include - * the protocol, eg http:// */ - FString InitialURL; - /** Should the browser window support transparency. */ - bool bSupportsTransparency = true; - bool bShowInitialThrobber = false; - - UPROPERTY() - class UImtblJSConnector* JSConnector = nullptr; - - void SetBrowserContent(); - - bool BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) const; - // Bind the JSConnector to the browser window - void BindConnector(); - -#if PLATFORM_ANDROID | PLATFORM_IOS - void HandleOnLoadCompleted(); -#endif - -#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 - void HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity); -#endif - - /** - * UWidget interface - */ -public: - virtual void ReleaseSlateResources(bool bReleaseChildren) override; - -protected: - virtual TSharedRef RebuildWidget() override; - virtual void OnWidgetRebuilt() override; -}; diff --git a/Source/Immutable/Private/Immutable/ImtblJSConnector.cpp b/Source/Immutable/Private/Immutable/ImtblJSConnector.cpp index 10a9352..ecc7403 100644 --- a/Source/Immutable/Private/Immutable/ImtblJSConnector.cpp +++ b/Source/Immutable/Private/Immutable/ImtblJSConnector.cpp @@ -2,10 +2,6 @@ #include "Immutable/ImtblJSConnector.h" -#include "Immutable/Misc/ImtblLogging.h" -#include "ImtblBrowserWidget.h" -#include "Immutable/ImtblJSMessages.h" - void UImtblJSConnector::Init(bool bPageLoaded) { IMTBL_LOG("JSConnect::Init called, bPageloaded %d", bPageLoaded); @@ -44,6 +40,52 @@ void UImtblJSConnector::AddCallbackWhenBridgeReady(const FOnBridgeReadyDelegate: } } +void UImtblJSConnector::SendToGame(const FString& Message) +{ + SendEventToGame(TEXT(""), Message); +} + +void UImtblJSConnector::SendEventToGame(const FString& Event, const FString& Message) +{ + IMTBL_LOG_FUNC("Received message from JS: %s", *Message); + + const TOptional Response = FImtblJSResponse::FromJsonString(Message); + + Internal_MulticastDelegate_OnEventToGame.Broadcast(Event, Message, Response); + Internal_DynamicMulticastDelegate_OnEventToGame.Broadcast(Event, Message, Response.IsSet(), Response.Get({})); + + if (!Response.IsSet()) + { + IMTBL_WARN("Received unexpected response from browser: %s", *Message); + return; + } + + // Handle response + + if (!RequestResponseDelegates.Contains(Response->requestId)) + { + IMTBL_WARN("No delegate found for response with id %s", *Response->requestId); + return; + } + + if (!RequestResponseDelegates[Response->requestId].ExecuteIfBound(Response.GetValue())) + { + IMTBL_WARN("Delegate for response with id %s failed to execute", *Response->requestId); + } + + RequestResponseDelegates.Remove(Response->requestId); +} + +FImmutableEventToGameMulticastDelegate* UImtblJSConnector::MulticastDelegate_OnEventToGame() +{ + return &Internal_MulticastDelegate_OnEventToGame; +} + +FImmutableEventToGameDynamicMulticastDelegate* UImtblJSConnector::DynamicMulticastDelegate_OnEventToGame() +{ + return &Internal_DynamicMulticastDelegate_OnEventToGame; +} + FString UImtblJSConnector::CallJS(const FString& Function, const FString& Data, const FImtblJSResponseDelegate& HandleResponse, const float ResponseTimeout) { const FString Guid = FGuid::NewGuid().ToString(); @@ -93,40 +135,12 @@ void UImtblJSConnector::HandleInitResponse(FImtblJSResponse Response) OnBridgeReady.Clear(); } -void UImtblJSConnector::SendToGame(FString Message) -{ - IMTBL_LOG_FUNC("Received message from JS: %s", *Message); - - // Parse response - - const TOptional Response = FImtblJSResponse::FromJsonString(Message); - if (!Response.IsSet()) - { - IMTBL_WARN("Received unexpected response from browser: %s", *Message); - return; - } - - // Handle response - - if (!RequestResponseDelegates.Contains(Response->requestId)) - { - IMTBL_WARN("No delegate found for response with id %s", *Response->requestId); - return; - } - - if (!RequestResponseDelegates[Response->requestId].ExecuteIfBound(Response.GetValue())) - { - IMTBL_WARN("Delegate for response with id %s failed to execute", *Response->requestId); - } - - RequestResponseDelegates.Remove(Response->requestId); -} - #if PLATFORM_ANDROID | PLATFORM_IOS -void UImtblJSConnector::SetMobileBridgeReady() { - IMTBL_LOG_FUNCSIG - bIsBridgeReady = true; - OnBridgeReady.Broadcast(); - OnBridgeReady.Clear(); +void UImtblJSConnector::SetMobileBridgeReady() +{ + IMTBL_LOG_FUNCSIG + bIsBridgeReady = true; + OnBridgeReady.Broadcast(); + OnBridgeReady.Clear(); } -#endif +#endif \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h b/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h index fe9f221..d6e32f2 100644 --- a/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h +++ b/Source/Immutable/Public/Immutable/Actions/ImtblConnectImxAsyncAction.h @@ -41,16 +41,19 @@ class IMMUTABLE_API UImtblConnectionAsyncActions : public UImtblBlueprintAsyncAc static UImtblConnectionAsyncActions* ConnectImx(UObject* WorldContextObject, const FImmutableDirectLoginOptions& DirectLoginOptions); virtual void Activate() override; + + FPassportConnectOutputPin* DynamicMulticastDelegate_OnSuccess(); + FPassportConnectOutputPin* DynamicMulticastDelegate_OnFailed(); private: void DoConnect(TWeakObjectPtr JSConnector); void OnConnect(FImmutablePassportResult Result); - UPROPERTY(BlueprintAssignable) - FPassportConnectOutputPin Success; - UPROPERTY(BlueprintAssignable) - FPassportConnectOutputPin Failed; + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "OnSuccess")) + FPassportConnectOutputPin Internal_DynamicMulticastDelegate_OnSuccess; + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "OnFailed")) + FPassportConnectOutputPin Internal_DynamicMulticastDelegate_OnFailed; bool bUseCachedSession = false; bool bIsConnectImx = false; diff --git a/Source/Immutable/Public/Immutable/Actions/ImtblEmbeddedLoginAsyncAction.h b/Source/Immutable/Public/Immutable/Actions/ImtblEmbeddedLoginAsyncAction.h new file mode 100644 index 0000000..91f5236 --- /dev/null +++ b/Source/Immutable/Public/Immutable/Actions/ImtblEmbeddedLoginAsyncAction.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Immutable/Actions/ImtblBlueprintAsyncAction.h" + +#include "ImtblEmbeddedLoginAsyncAction.generated.h" + +class UImmutableJSConnectorBrowserWidget; + +UCLASS() +class IMMUTABLE_API UImtblEmbeddedLoginAsyncAction : public UImtblBlueprintAsyncAction +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable") + static UImtblEmbeddedLoginAsyncAction* Login(UObject* WorldContextObject, UImmutableJSConnectorBrowserWidget* JSConnectorBrowserWidget); + + virtual void Activate() override; + virtual void SetReadyToDestroy() override; + + UImtblConnectionAsyncActions* GetLoginAsyncAction() const; + + FImmutableMessageMulticastDelegate* LoginFailed_MulticastDelegate(); + FImmutableMessageDynamicMulticastDelegate* LoginFailed_DynamicMulticastDelegate(); + + FSimpleMulticastDelegate* LoginSuccess_MulticastDelegate(); + FImmutableSimpleDynamicMulticastDelegate* LoginSuccess_DynamicMulticastDelegate(); + + FSimpleMulticastDelegate* Closed_MulticastDelegate(); + FImmutableSimpleDynamicMulticastDelegate* Closed_DynamicMulticastDelegate(); + +protected: + UFUNCTION() + void LoginAsyncAction_DynamicMulticastDelegate_OnSuccess(FString String); + + UFUNCTION() + void LoginAsyncAction_DynamicMulticastDelegate_OnFailed(FString String); + +protected: + TWeakObjectPtr BindedWorld; + TWeakObjectPtr BindedJSConnectorBrowserWidget; + + UPROPERTY() + TObjectPtr LoginAsyncAction; + + FImmutableMessageMulticastDelegate Internal_LoginFailed_MulticastDelegate; + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "LoginFailed")) + FImmutableMessageDynamicMulticastDelegate Internal_LoginFailed_DynamicMulticastDelegate; + + FSimpleMulticastDelegate Internal_LoginSuccess_MulticastDelegate; + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "LoginSuccess")) + FImmutableSimpleDynamicMulticastDelegate Internal_LoginSuccess_DynamicMulticastDelegate; + + FSimpleMulticastDelegate Internal_Closed_MulticastDelegate; + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "Closed")) + FImmutableSimpleDynamicMulticastDelegate Internal_Closed_DynamicMulticastDelegate; +}; \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/Browser/ImmutableBaseBrowserWidget.h b/Source/Immutable/Public/Immutable/Browser/ImmutableBaseBrowserWidget.h new file mode 100644 index 0000000..7d660d4 --- /dev/null +++ b/Source/Immutable/Public/Immutable/Browser/ImmutableBaseBrowserWidget.h @@ -0,0 +1,84 @@ +#pragma once + +#include "Components/Widget.h" + +#if USING_BUNDLED_CEF +#include "IWebBrowserWindow.h" +#endif + +#include "ImmutableBaseBrowserWidget.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FImmutableBrowserConsoleMessageDynamicMulticastDelegate, const FString&, Message, const FString&, Source, int32, Line, int32, Severity); + +/** + * Abstract base class for browser widgets providing core browser functionality + * Derived classes can override template methods to customize behavior + */ +UCLASS(Abstract) +class IMMUTABLE_API UImmutableBaseBrowserWidget : public UWidget +{ + GENERATED_BODY() + +public: + /** UVisual: @Interface @Begin */ + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + /** UVisual: @Interface @End */ + + /** + * Check if a page is currently loaded + * @return True if a page is loaded + */ + UFUNCTION(BlueprintPure) + virtual bool IsPageLoaded() const; + + UFUNCTION(BlueprintCallable) + void LoadURL(FString NewURL) const; + + UFUNCTION(BlueprintCallable) + virtual void LoadString(FString Contents, FString DummyURL); + + FImmutableBrowserConsoleMessageDynamicMulticastDelegate* DynamicMulticastDelegate_OnConsoleMessage(); + FSimpleMulticastDelegate MulticastDelegate_OnLoadCompleted(); + FSimpleMulticastDelegate* MulticastDelegate_OnBrowserCreated(); + +protected: + /** UWidget: @Interface @Begin */ + virtual TSharedRef RebuildWidget() override; + virtual void OnWidgetRebuilt() override; + /** UWidget: @Interface @End */ + +#if PLATFORM_ANDROID || PLATFORM_IOS + virtual void HandleOnLoadCompleted(); +#endif + +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 + void HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity); +#endif + + virtual void HandleLoadCompleted(); + virtual void HandleConsoleMessage(const FString& Message, const FString& Source, int32 Line, int32 Severity); + + virtual void OnBrowserCreated(); + +protected: + /** Called when console messages are logged */ + UPROPERTY(BlueprintAssignable, Category = "Browser|Events", Meta = (DisplayName = "On Console Message")) + FImmutableBrowserConsoleMessageDynamicMulticastDelegate Internal_DynamicMulticastDelegate_OnConsoleMessage; + + FSimpleMulticastDelegate Internal_MulticastDelegate_OnLoadCompleted; + FSimpleMulticastDelegate Internal_MulticastDelegate_OnBrowserCreated; + +#if USING_BUNDLED_CEF + /** The underlying Slate web browser widget */ + TSharedPtr WebBrowserWidget; +#endif + + /** URL that the browser will initially navigate to */ + FString InitialURL = TEXT("about:blank"); + + /** Whether the browser supports transparency */ + bool bSupportsTransparency = true; + + /** Whether to show initial loading throbber */ + bool bShowInitialThrobber = false; +}; \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/Browser/ImmutableJSConnectorBrowserWidget.h b/Source/Immutable/Public/Immutable/Browser/ImmutableJSConnectorBrowserWidget.h new file mode 100644 index 0000000..68003e6 --- /dev/null +++ b/Source/Immutable/Public/Immutable/Browser/ImmutableJSConnectorBrowserWidget.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ImmutableBaseBrowserWidget.h" + +#include "ImmutableJSConnectorBrowserWidget.generated.h" + +UCLASS() +class IMMUTABLE_API UImmutableJSConnectorBrowserWidget : public UImmutableBaseBrowserWidget +{ + GENERATED_BODY() + +public: + /** UObject: @Interface @Begin */ + virtual void PostInitProperties() override; + /** UObject: @Interface @End */ + + UFUNCTION(BlueprintPure) + UImtblJSConnector* GetJSConnector() const; + + /** + * Execute JavaScript in the browser context + * @param ScriptText The JavaScript code to execute + */ + UFUNCTION(BlueprintCallable, Category = "Immutable|Browser") + virtual void ExecuteJavaScript(const FString& ScriptText) const; + + /** + * Bind a UObject to be accessible from JavaScript + * @param Name The name to use in JavaScript (window.Name) + * @param Object The object to bind + * @param bIsPermanent Whether the binding persists across page loads + * @return True if binding was successful + */ + virtual bool BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) const; + +protected: + /** UImmutableBaseBrowserWidget: @Interface @Begin */ + virtual void OnBrowserCreated() override; + /** UImmutableBaseBrowserWidget: @Interface @End */ + + /** + * Set up JavaScript bindings + * Override to bind custom objects + */ + virtual void SetupJavaScriptBindings(); + +protected: + UPROPERTY(Transient) + TObjectPtr JSConnector; +}; \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/ImmutableDataTypes.h b/Source/Immutable/Public/Immutable/ImmutableDataTypes.h index 17616b0..1589c99 100644 --- a/Source/Immutable/Public/Immutable/ImmutableDataTypes.h +++ b/Source/Immutable/Public/Immutable/ImmutableDataTypes.h @@ -7,6 +7,11 @@ #include "ImmutableDataTypes.generated.h" +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FImmutableSimpleDynamicMulticastDelegate); + +DECLARE_MULTICAST_DELEGATE_OneParam(FImmutableMessageMulticastDelegate, const FString& /* Message */); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FImmutableMessageDynamicMulticastDelegate, const FString&, Message); + DECLARE_MULTICAST_DELEGATE_OneParam(FImmutableDeepLinkMulticastDelegate, const FString& /** DeepLink */); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FImmutableDeepLinkDynamicMulticastDelegate, const FString&, DeepLink); @@ -60,6 +65,19 @@ struct IMMUTABLE_API FImmutableDirectLoginOptions /** Marketing consent status for authentication (defaults to opted in) */ UPROPERTY(EditAnywhere, BlueprintReadWrite) EImmutableMarketingConsentStatus MarketingConsentStatus = EImmutableMarketingConsentStatus::Opted_In; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString ImPassportTraceId; +}; + +UCLASS() +class UImmutableDirectLoginOptionsStatics : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable) + static bool FromJSResponse(const FImtblJSResponse& Response, FImmutableDirectLoginOptions& DirectLoginOptions); }; USTRUCT() @@ -215,6 +233,8 @@ struct IMMUTABLE_API FImmutablePassportResult { GENERATED_BODY() + FString ToJsonString() const; + /** Whether the response was successful. */ UPROPERTY() bool Success = false; @@ -224,6 +244,7 @@ struct IMMUTABLE_API FImmutablePassportResult FString Error; /** Response payload. */ + UPROPERTY() FImtblJSResponse Response; }; diff --git a/Source/Immutable/Public/Immutable/ImtblJSConnector.h b/Source/Immutable/Public/Immutable/ImtblJSConnector.h index 63ea626..cb1e3c8 100644 --- a/Source/Immutable/Public/Immutable/ImtblJSConnector.h +++ b/Source/Immutable/Public/Immutable/ImtblJSConnector.h @@ -10,6 +10,8 @@ // clang-format on DECLARE_DELEGATE_OneParam(FImtblJSResponseDelegate, struct FImtblJSResponse); +DECLARE_MULTICAST_DELEGATE_ThreeParams(FImmutableEventToGameMulticastDelegate, const FString& /* Event */, const FString& /* Message */, const TOptional& /* Response */); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FImmutableEventToGameDynamicMulticastDelegate, const FString&, Event, const FString&, Message, bool, bResponseIsSet, const FImtblJSResponse&, Response); /** * JSConnector UObject to bind with a browser widget. @@ -21,7 +23,7 @@ DECLARE_DELEGATE_OneParam(FImtblJSResponseDelegate, struct FImtblJSResponse); * of the base classes in the UObject hierarchy have any UFUNCTIONs we can be * sure that the only UFUNCTIONs exposed to the browser are defined here. */ -UCLASS() +UCLASS(BlueprintType) class IMMUTABLE_API UImtblJSConnector : public UObject { GENERATED_BODY() @@ -47,20 +49,31 @@ class IMMUTABLE_API UImtblJSConnector : public UObject // Callback for JavaScript to send responses back to Unreal UFUNCTION() - void SendToGame(FString Message); + void SendToGame(const FString& Message); + + UFUNCTION() + void SendEventToGame(const FString& Event, const FString& Message); + + FImmutableEventToGameMulticastDelegate* MulticastDelegate_OnEventToGame(); + FImmutableEventToGameDynamicMulticastDelegate* DynamicMulticastDelegate_OnEventToGame(); // Bind the func to be called for executing JS. Typically by the BrowserWidget // (UE5) or Blui for UE4 FOnExecuteJsDelegate ExecuteJs; #if PLATFORM_ANDROID | PLATFORM_IOS - void SetMobileBridgeReady(); + void SetMobileBridgeReady(); #endif -protected: // Call a JavaScript function in the connected browser FString CallJS(const FString& Function, const FString& Data, const FImtblJSResponseDelegate& HandleResponse, float ResponseTimeout = 0.0f); +protected: + FImmutableEventToGameMulticastDelegate Internal_MulticastDelegate_OnEventToGame; + + UPROPERTY(BlueprintAssignable, Meta = (DisplayName = "OnEventToGame")) + FImmutableEventToGameDynamicMulticastDelegate Internal_DynamicMulticastDelegate_OnEventToGame; + private: FOnBridgeReadyDelegate OnBridgeReady; TMap RequestResponseDelegates; @@ -71,4 +84,4 @@ class IMMUTABLE_API UImtblJSConnector : public UObject // Call a JavaScript function in the connected browser void CallJS(const FImtblJSRequest& Request, FImtblJSResponseDelegate HandleResponse, float ResponseTimeout = 0.0f); -}; +}; \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/ImtblJSMessages.h b/Source/Immutable/Public/Immutable/ImtblJSMessages.h index 53470d9..f280a64 100644 --- a/Source/Immutable/Public/Immutable/ImtblJSMessages.h +++ b/Source/Immutable/Public/Immutable/ImtblJSMessages.h @@ -64,7 +64,7 @@ struct IMMUTABLE_API FImtblJSRequest } }; -USTRUCT() +USTRUCT(BlueprintType) struct IMMUTABLE_API FImtblJSResponse { GENERATED_BODY()