Skip to content

Commit 3328274

Browse files
feat: add embedded login support (#3972)
1 parent b6bca3d commit 3328274

19 files changed

+497
-318
lines changed
2.15 KB
Binary file not shown.
732 Bytes
Binary file not shown.
69.3 KB
Binary file not shown.

Source/Immutable/Private/Immutable/Actions/ImtblConnectImxAsyncAction.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,24 @@ void UImtblConnectionAsyncActions::Activate()
3737
{
3838
FString Error = "Connect failed due to missing world or world context object.";
3939
IMTBL_WARN("%s", *Error)
40-
Failed.Broadcast(Error);
40+
Internal_DynamicMulticastDelegate_OnFailed.Broadcast(Error);
4141

4242
return;
4343
}
4444

4545
GetSubsystem()->WhenReady(this, &UImtblConnectionAsyncActions::DoConnect);
4646
}
4747

48+
UImtblConnectionAsyncActions::FPassportConnectOutputPin* UImtblConnectionAsyncActions::DynamicMulticastDelegate_OnSuccess()
49+
{
50+
return &Internal_DynamicMulticastDelegate_OnSuccess;
51+
}
52+
53+
UImtblConnectionAsyncActions::FPassportConnectOutputPin* UImtblConnectionAsyncActions::DynamicMulticastDelegate_OnFailed()
54+
{
55+
return &Internal_DynamicMulticastDelegate_OnFailed;
56+
}
57+
4858
void UImtblConnectionAsyncActions::DoConnect(TWeakObjectPtr<UImtblJSConnector> JSConnector)
4959
{
5060
auto Passport = GetSubsystem()->GetPassport();
@@ -68,10 +78,10 @@ void UImtblConnectionAsyncActions::OnConnect(FImmutablePassportResult Result)
6878
{
6979
if (Result.Success)
7080
{
71-
Success.Broadcast(TEXT(""));
81+
Internal_DynamicMulticastDelegate_OnSuccess.Broadcast(TEXT(""));
7282
}
7383
else
7484
{
75-
Failed.Broadcast(Result.Error);
85+
Internal_DynamicMulticastDelegate_OnFailed.Broadcast(Result.Error);
7686
}
7787
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#include "Immutable/Browser/ImmutableBaseBrowserWidget.h"
2+
3+
#include "Immutable/Misc/ImtblLogging.h"
4+
5+
#define LOCTEXT_NAMESPACE "BaseBrowserWidget"
6+
7+
void UImmutableBaseBrowserWidget::ReleaseSlateResources(bool bReleaseChildren)
8+
{
9+
Super::ReleaseSlateResources(bReleaseChildren);
10+
11+
#if USING_BUNDLED_CEF
12+
WebBrowserWidget.Reset();
13+
#endif
14+
}
15+
16+
bool UImmutableBaseBrowserWidget::IsPageLoaded() const
17+
{
18+
#if USING_BUNDLED_CEF
19+
return WebBrowserWidget.IsValid() && WebBrowserWidget->IsLoaded();
20+
#endif
21+
return false;
22+
}
23+
24+
void UImmutableBaseBrowserWidget::LoadURL(FString NewURL) const
25+
{
26+
#if USING_BUNDLED_CEF
27+
if (WebBrowserWidget.IsValid())
28+
{
29+
return WebBrowserWidget->LoadURL(NewURL);
30+
}
31+
#endif
32+
}
33+
34+
void UImmutableBaseBrowserWidget::LoadString(FString Contents, FString DummyURL)
35+
{
36+
#if USING_BUNDLED_CEF
37+
if (WebBrowserWidget.IsValid())
38+
{
39+
WebBrowserWidget->LoadString(Contents, DummyURL);
40+
}
41+
#endif
42+
}
43+
44+
FImmutableBrowserConsoleMessageDynamicMulticastDelegate* UImmutableBaseBrowserWidget::DynamicMulticastDelegate_OnConsoleMessage()
45+
{
46+
return &Internal_DynamicMulticastDelegate_OnConsoleMessage;
47+
}
48+
49+
FSimpleMulticastDelegate UImmutableBaseBrowserWidget::MulticastDelegate_OnLoadCompleted()
50+
{
51+
return Internal_MulticastDelegate_OnLoadCompleted;
52+
}
53+
54+
FSimpleMulticastDelegate* UImmutableBaseBrowserWidget::MulticastDelegate_OnBrowserCreated()
55+
{
56+
return &Internal_MulticastDelegate_OnBrowserCreated;
57+
}
58+
59+
TSharedRef<SWidget> UImmutableBaseBrowserWidget::RebuildWidget()
60+
{
61+
if (IsDesignTime())
62+
{
63+
// Show placeholder in editor
64+
return SNew(SBox)
65+
.HAlign(HAlign_Center)
66+
.VAlign(VAlign_Center)
67+
[
68+
SNew(STextBlock)
69+
.Text(LOCTEXT("BrowserPlaceholder", "Browser Widget"))
70+
];
71+
}
72+
else
73+
{
74+
#if USING_BUNDLED_CEF
75+
// Create the web browser widget
76+
WebBrowserWidget = SNew(SWebBrowser)
77+
.InitialURL(InitialURL)
78+
.ShowControls(false)
79+
.SupportsTransparency(bSupportsTransparency)
80+
.ShowInitialThrobber(bShowInitialThrobber)
81+
#if PLATFORM_ANDROID || PLATFORM_IOS
82+
.OnLoadCompleted(BIND_UOBJECT_DELEGATE(FSimpleDelegate, HandleOnLoadCompleted))
83+
#endif
84+
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
85+
.OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage))
86+
#endif
87+
;
88+
89+
return WebBrowserWidget.ToSharedRef();
90+
#else
91+
// Fallback for non-CEF builds
92+
return
93+
SNew(SBox)
94+
.HAlign(HAlign_Center)
95+
.VAlign(VAlign_Center)
96+
[
97+
SNew(STextBlock)
98+
.Text(LOCTEXT("NoCEF", "Browser Not Available"))
99+
];
100+
#endif
101+
}
102+
}
103+
104+
void UImmutableBaseBrowserWidget::OnWidgetRebuilt()
105+
{
106+
Super::OnWidgetRebuilt();
107+
108+
OnBrowserCreated();
109+
}
110+
111+
#if PLATFORM_ANDROID || PLATFORM_IOS
112+
void UImmutableBaseBrowserWidget::HandleOnLoadCompleted()
113+
{
114+
// Default mobile load handling
115+
HandleLoadCompleted();
116+
}
117+
#endif
118+
119+
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1
120+
void UImmutableBaseBrowserWidget::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity)
121+
{
122+
UE_LOG(LogImmutable, Log, TEXT("Browser console message: %s, Source: %s, Line: %d, Severity: %d"), *Message, *Source, Line, Severity);
123+
HandleConsoleMessage(Message, Source, Line, static_cast<int32>(Severity));
124+
}
125+
#endif
126+
127+
void UImmutableBaseBrowserWidget::HandleLoadCompleted()
128+
{
129+
Internal_MulticastDelegate_OnLoadCompleted.Broadcast();
130+
}
131+
132+
void UImmutableBaseBrowserWidget::HandleConsoleMessage(const FString& Message, const FString& Source, int32 Line, int32 Severity)
133+
{
134+
Internal_DynamicMulticastDelegate_OnConsoleMessage.Broadcast(Message, Source, Line, Severity);
135+
}
136+
137+
void UImmutableBaseBrowserWidget::OnBrowserCreated()
138+
{
139+
Internal_MulticastDelegate_OnBrowserCreated.Broadcast();
140+
}
141+
142+
#undef LOCTEXT_NAMESPACE
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include "Immutable/Browser/ImmutableJSConnectorBrowserWidget.h"
2+
3+
#include "Immutable/ImtblJSConnector.h"
4+
5+
void UImmutableJSConnectorBrowserWidget::PostInitProperties()
6+
{
7+
Super::PostInitProperties();
8+
9+
if (!IsTemplate())
10+
{
11+
JSConnector = NewObject<UImtblJSConnector>(this);
12+
JSConnector->ExecuteJs.BindUObject(this, &ThisClass::ExecuteJavaScript);
13+
}
14+
}
15+
16+
UImtblJSConnector* UImmutableJSConnectorBrowserWidget::GetJSConnector() const
17+
{
18+
return JSConnector;
19+
}
20+
21+
void UImmutableJSConnectorBrowserWidget::ExecuteJavaScript(const FString& ScriptText) const
22+
{
23+
#if USING_BUNDLED_CEF
24+
if (WebBrowserWidget.IsValid())
25+
{
26+
WebBrowserWidget->ExecuteJavascript(ScriptText);
27+
}
28+
#endif
29+
}
30+
31+
bool UImmutableJSConnectorBrowserWidget::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent) const
32+
{
33+
#if USING_BUNDLED_CEF
34+
if (!WebBrowserWidget.IsValid() || !Object)
35+
{
36+
UE_LOG(LogImmutable, Warning, TEXT("Could not bind UObject '%s' to browser, WebBrowserWidget is null"), *Object->GetName());
37+
return false;
38+
}
39+
40+
WebBrowserWidget->BindUObject(Name, Object, bIsPermanent);
41+
return true;
42+
#endif
43+
return false;
44+
}
45+
46+
void UImmutableJSConnectorBrowserWidget::OnBrowserCreated()
47+
{
48+
Super::OnBrowserCreated();
49+
50+
SetupJavaScriptBindings();
51+
}
52+
53+
void UImmutableJSConnectorBrowserWidget::SetupJavaScriptBindings()
54+
{
55+
if (JSConnector && JSConnector->IsBound())
56+
{
57+
return;
58+
}
59+
60+
if (JSConnector)
61+
{
62+
if (BindUObject(UImtblJSConnector::JSObjectName(), JSConnector))
63+
{
64+
JSConnector->Init(IsPageLoaded());
65+
}
66+
}
67+
}

Source/Immutable/Private/Immutable/ImmutableDataTypes.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include "Immutable/ImmutableDataTypes.h"
44

5+
#include "Immutable/Actions/ImtblConnectImxAsyncAction.h"
6+
57
#if PLATFORM_WINDOWS
68
#include "Immutable/Windows/ImmutablePKCEWindows.h"
79
#endif
@@ -115,4 +117,9 @@ TSharedPtr<FJsonObject> FImmutableDirectLoginOptions::ToJsonObject() const
115117
JsonObject->SetStringField(TEXT("marketingConsentStatus"), StaticEnum<EImmutableMarketingConsentStatus>()->GetNameStringByValue(static_cast<int64>(MarketingConsentStatus)).ToLower());
116118

117119
return JsonObject;
120+
}
121+
122+
bool UImmutableDirectLoginOptionsStatics::FromJSResponse(const FImtblJSResponse& Response, FImmutableDirectLoginOptions& DirectLoginOptions)
123+
{
124+
return FJsonObjectConverter::JsonObjectToUStruct(Response.JsonObject.ToSharedRef(), &DirectLoginOptions);
118125
}

Source/Immutable/Private/Immutable/ImmutablePassport.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ void UImmutablePassport::Connect(bool IsConnectImx, const FImtblPassportResponse
9494
#if PLATFORM_WINDOWS
9595
// Verify PKCEData is null before initializing to ensure we're not overriding an active PKCE operation.
9696
// A non-null value indicates another PKCE operation is already in progress.
97-
ensureAlways(!PKCEData);
9897
PKCEData = UImmutablePKCEWindows::Initialise(InitData);
9998
if (PKCEData)
10099
{

Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.cpp

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
#include "Blueprint/WidgetTree.h"
66
#include "Components/CanvasPanel.h"
77
#include "Components/CanvasPanelSlot.h"
8-
#include "Components/PanelWidget.h"
98
#include "Components/ScaleBox.h"
109
#include "Components/ScaleBoxSlot.h"
10+
11+
#include "Immutable/Browser/ImmutableJSConnectorBrowserWidget.h"
12+
#include "Immutable/ImmutableUtilities.h"
1113
#include "Immutable/Misc/ImtblLogging.h"
12-
#include "ImtblBrowserWidget.h"
13-
#include "Immutable/ImtblJSConnector.h"
1414

1515
TSharedRef<SWidget> UImtblBrowserUserWidget::RebuildWidget()
1616
{
@@ -35,21 +35,47 @@ TSharedRef<SWidget> UImtblBrowserUserWidget::RebuildWidget()
3535
RootWidget->AddChild(ScaleBox);
3636
if (ScaleBox)
3737
{
38-
Browser = WidgetTree->ConstructWidget<UImtblBrowserWidget>(UImtblBrowserWidget::StaticClass(), TEXT("ImmutableBrowserWidget"));
39-
ScaleBox->AddChild(Browser);
38+
W_Browser = WidgetTree->ConstructWidget<UImmutableJSConnectorBrowserWidget>(UImmutableJSConnectorBrowserWidget::StaticClass(), TEXT("GameBridgeWidget"));
39+
W_Browser->MulticastDelegate_OnLoadCompleted().AddWeakLambda(this, []()
40+
{
41+
#if PLATFORM_ANDROID | PLATFORM_IOS
42+
FString IndexURL = "file:///immutable/index.html";
43+
44+
#if USING_BUNDLED_CEF
45+
if (WebBrowserWidget->GetUrl() == IndexURL)
46+
{
47+
JSConnector->SetMobileBridgeReady();
48+
}
49+
else
50+
{
51+
UE_LOG(LogImmutable, Error, TEXT("Immutable Browser Widget Url don't match: (loaded : %s, required: %s)"), *WebBrowserWidget->GetUrl(), *IndexURL);
52+
}
53+
#endif
54+
#endif
55+
});
56+
W_Browser->MulticastDelegate_OnBrowserCreated()->AddWeakLambda(this, [this]()
57+
{
58+
FString JavaScript;
59+
if (FImmutableUtilities::LoadGameBridge(JavaScript))
60+
{
61+
FString IndexHtml = FString("<!doctype html><html lang='en'><head><meta " "charset='utf-8'><title>GameSDK Bridge</title><script>") + JavaScript + FString("</script></head><body><h1>Bridge Running</h1></body></html>");
62+
W_Browser->LoadString(IndexHtml, TEXT("file:///immutable/index.html"));
63+
}
64+
});
65+
ScaleBox->AddChild(W_Browser);
4066
if (UCanvasPanelSlot* RootWidgetSlot = Cast<UCanvasPanelSlot>(ScaleBox->Slot))
4167
{
4268
#if PLATFORM_ANDROID | PLATFORM_IOS
43-
// Android webview needs to be at least 1px to 1px big to work
44-
// but it can be off screen
45-
RootWidgetSlot->SetAnchors(FAnchors(0, 0, 0, 0));
46-
RootWidgetSlot->SetOffsets(FMargin(-1, -1, 1, 1));
69+
// Android webview needs to be at least 1px to 1px big to work
70+
// but it can be off screen
71+
RootWidgetSlot->SetAnchors(FAnchors(0, 0, 0, 0));
72+
RootWidgetSlot->SetOffsets(FMargin(-1, -1, 1, 1));
4773
#else
4874
RootWidgetSlot->SetAnchors(FAnchors(0, 0, 1, 1));
4975
RootWidgetSlot->SetOffsets(DefaultOffsets);
5076
#endif
5177
}
52-
if (UScaleBoxSlot* ScaleBoxSlot = Cast<UScaleBoxSlot>(Browser->Slot))
78+
if (UScaleBoxSlot* ScaleBoxSlot = Cast<UScaleBoxSlot>(W_Browser->Slot))
5379
{
5480
ScaleBoxSlot->SetHorizontalAlignment(HAlign_Fill);
5581
ScaleBoxSlot->SetVerticalAlignment(VAlign_Fill);
@@ -87,11 +113,11 @@ void UImtblBrowserUserWidget::OnWidgetRebuilt()
87113

88114
TWeakObjectPtr<class UImtblJSConnector> UImtblBrowserUserWidget::GetJSConnector() const
89115
{
90-
if (!Browser)
116+
if (!W_Browser)
91117
{
92118
IMTBL_WARN("JSConnector requested before Browser was initialized");
93119
return nullptr;
94120
}
95121

96-
return Browser->GetJSConnector();
97-
}
122+
return W_Browser->GetJSConnector();
123+
}

Source/Immutable/Private/Immutable/ImtblBrowserUserWidget.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Fill out your copyright notice in the Description page of Project Settings.
2-
3-
#pragma once
1+
#pragma once
42

53
#include "Blueprint/UserWidget.h"
6-
#include "CoreMinimal.h"
4+
75
#include "ImtblBrowserUserWidget.generated.h"
86

7+
class UImmutableJSConnectorBrowserWidget;
8+
99
UCLASS()
1010
class IMMUTABLE_API UImtblBrowserUserWidget : public UUserWidget
1111
{
@@ -21,11 +21,11 @@ class IMMUTABLE_API UImtblBrowserUserWidget : public UUserWidget
2121

2222
private:
2323
UPROPERTY()
24-
class UImtblBrowserWidget* Browser = nullptr;
24+
TObjectPtr<UImmutableJSConnectorBrowserWidget> W_Browser;
2525

2626
bool bIsBrowserAppInitialized = false;
2727

2828
FTimerHandle Timer;
2929

3030
FMargin DefaultOffsets = FMargin(150, 150, 150, 150);
31-
};
31+
};

0 commit comments

Comments
 (0)