Skip to content

Commit d35afac

Browse files
authored
Merge branch 'dev' into bugsweep/aug16-consolidated
2 parents 5a374d8 + 1bd63e7 commit d35afac

37 files changed

Lines changed: 932 additions & 130 deletions

Explorer/Assets/DCL/AuthenticationScreenFlow/Assets/Prefabs/Lobby.NewAccount.Screen.prefab

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,8 @@ MonoBehaviour:
12521252
<ProfileNameInputField>k__BackingField: {fileID: 2960017395348409964}
12531253
<BackButton>k__BackingField: {fileID: 3836947382991193770}
12541254
<FinalizeNewUserButton>k__BackingField: {fileID: 3173969130928085357}
1255+
<JumpInIcon>k__BackingField: {fileID: 7198789231518003281}
1256+
<FinalizeLoading>k__BackingField: {fileID: 3407687760501345149}
12551257
<RandomizeButton>k__BackingField: {fileID: 5000000000000000004}
12561258
<BodyTypeDropdownButton>k__BackingField: {fileID: 6458339435592951374}
12571259
<BodyTypeDropdownPanel>k__BackingField: {fileID: 6347562781986431726}
@@ -3293,7 +3295,10 @@ PrefabInstance:
32933295
m_RemovedComponents:
32943296
- {fileID: 1437024038448806854, guid: 83437f351686bca419378abea4d2ed07, type: 3}
32953297
m_RemovedGameObjects: []
3296-
m_AddedGameObjects: []
3298+
m_AddedGameObjects:
3299+
- targetCorrespondingSourceObject: {fileID: 3152644595163223519, guid: 83437f351686bca419378abea4d2ed07, type: 3}
3300+
insertIndex: -1
3301+
addedObject: {fileID: 2238380306874217515}
32973302
m_AddedComponents:
32983303
- targetCorrespondingSourceObject: {fileID: 2702495720024624198, guid: 83437f351686bca419378abea4d2ed07, type: 3}
32993304
insertIndex: -1
@@ -3372,6 +3377,122 @@ MonoBehaviour:
33723377
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
33733378
m_Name:
33743379
m_EditorClassIdentifier:
3380+
--- !u!1 &7198789231518003281 stripped
3381+
GameObject:
3382+
m_CorrespondingSourceObject: {fileID: 5736390777860353485, guid: 83437f351686bca419378abea4d2ed07, type: 3}
3383+
m_PrefabInstance: {fileID: 3205715963961466268}
3384+
m_PrefabAsset: {fileID: 0}
3385+
--- !u!1001 &4868343409493379836
3386+
PrefabInstance:
3387+
m_ObjectHideFlags: 0
3388+
serializedVersion: 2
3389+
m_Modification:
3390+
serializedVersion: 3
3391+
m_TransformParent: {fileID: 557474527508596803}
3392+
m_Modifications:
3393+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3394+
propertyPath: m_Pivot.x
3395+
value: 0.5
3396+
objectReference: {fileID: 0}
3397+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3398+
propertyPath: m_Pivot.y
3399+
value: 0.5
3400+
objectReference: {fileID: 0}
3401+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3402+
propertyPath: m_AnchorMax.x
3403+
value: 0.5
3404+
objectReference: {fileID: 0}
3405+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3406+
propertyPath: m_AnchorMax.y
3407+
value: 0.5
3408+
objectReference: {fileID: 0}
3409+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3410+
propertyPath: m_AnchorMin.x
3411+
value: 0.5
3412+
objectReference: {fileID: 0}
3413+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3414+
propertyPath: m_AnchorMin.y
3415+
value: 0.5
3416+
objectReference: {fileID: 0}
3417+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3418+
propertyPath: m_SizeDelta.x
3419+
value: 30
3420+
objectReference: {fileID: 0}
3421+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3422+
propertyPath: m_SizeDelta.y
3423+
value: 30
3424+
objectReference: {fileID: 0}
3425+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3426+
propertyPath: m_LocalPosition.x
3427+
value: 0
3428+
objectReference: {fileID: 0}
3429+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3430+
propertyPath: m_LocalPosition.y
3431+
value: 0
3432+
objectReference: {fileID: 0}
3433+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3434+
propertyPath: m_LocalPosition.z
3435+
value: 0
3436+
objectReference: {fileID: 0}
3437+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3438+
propertyPath: m_LocalRotation.w
3439+
value: 1
3440+
objectReference: {fileID: 0}
3441+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3442+
propertyPath: m_LocalRotation.x
3443+
value: -0
3444+
objectReference: {fileID: 0}
3445+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3446+
propertyPath: m_LocalRotation.y
3447+
value: -0
3448+
objectReference: {fileID: 0}
3449+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3450+
propertyPath: m_LocalRotation.z
3451+
value: -0
3452+
objectReference: {fileID: 0}
3453+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3454+
propertyPath: m_AnchoredPosition.x
3455+
value: 42
3456+
objectReference: {fileID: 0}
3457+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3458+
propertyPath: m_AnchoredPosition.y
3459+
value: 0
3460+
objectReference: {fileID: 0}
3461+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3462+
propertyPath: m_LocalEulerAnglesHint.x
3463+
value: 0
3464+
objectReference: {fileID: 0}
3465+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3466+
propertyPath: m_LocalEulerAnglesHint.y
3467+
value: 0
3468+
objectReference: {fileID: 0}
3469+
- target: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3470+
propertyPath: m_LocalEulerAnglesHint.z
3471+
value: -336.96002
3472+
objectReference: {fileID: 0}
3473+
- target: {fileID: 7837773527243981185, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3474+
propertyPath: m_Name
3475+
value: LoadingSpinnerBlack
3476+
objectReference: {fileID: 0}
3477+
- target: {fileID: 7837773527243981185, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3478+
propertyPath: m_IsActive
3479+
value: 0
3480+
objectReference: {fileID: 0}
3481+
m_RemovedComponents: []
3482+
m_RemovedGameObjects: []
3483+
m_AddedGameObjects: []
3484+
m_AddedComponents: []
3485+
m_SourcePrefab: {fileID: 100100000, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3486+
--- !u!224 &2238380306874217515 stripped
3487+
RectTransform:
3488+
m_CorrespondingSourceObject: {fileID: 6674202217144139479, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3489+
m_PrefabInstance: {fileID: 4868343409493379836}
3490+
m_PrefabAsset: {fileID: 0}
3491+
--- !u!1 &3407687760501345149 stripped
3492+
GameObject:
3493+
m_CorrespondingSourceObject: {fileID: 7837773527243981185, guid: 252c1fea75fc0614587349a368eb8684, type: 3}
3494+
m_PrefabInstance: {fileID: 4868343409493379836}
3495+
m_PrefabAsset: {fileID: 0}
33753496
--- !u!1001 &5669827255451558947
33763497
PrefabInstance:
33773498
m_ObjectHideFlags: 0

Explorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public enum AuthStatus
6363
private readonly IWebRequestController webRequestController;
6464
private readonly IDecentralandUrlsSource decentralandUrlsSource;
6565
private readonly ProfileChangesBus profileChangesBus;
66+
private readonly string? referrer;
6667

6768
private AuthenticationScreenCharacterPreviewController? characterPreviewController;
6869
private readonly IInputBlock inputBlock;
@@ -111,7 +112,8 @@ public AuthenticationScreenController(
111112
IWearablesProvider wearablesProvider,
112113
IWebRequestController webRequestController,
113114
IDecentralandUrlsSource decentralandUrlsSource,
114-
ProfileChangesBus profileChangesBus)
115+
ProfileChangesBus profileChangesBus,
116+
string? referrer = null)
115117
: base(viewFactory)
116118
{
117119
this.web3Authenticator = web3Authenticator;
@@ -131,6 +133,7 @@ public AuthenticationScreenController(
131133
this.webRequestController = webRequestController;
132134
this.decentralandUrlsSource = decentralandUrlsSource;
133135
this.profileChangesBus = profileChangesBus;
136+
this.referrer = referrer;
134137
}
135138

136139
public override void Dispose()
@@ -170,7 +173,7 @@ protected override void OnViewInstantiated()
170173
new ProfileFetchingAuthState(fsm, viewInstance, this, CurrentState, selfProfile, storedIdentityProvider),
171174
new IdentityVerificationDappDeepLinkAuthState(fsm, viewInstance, this, CurrentState, web3Authenticator),
172175
new LobbyForExistingAccountAuthState(fsm, viewInstance, this, splashScreen, CurrentState, characterPreviewController),
173-
new LobbyForNewAccountAuthState(fsm, viewInstance, this, CurrentState, characterPreviewController, selfProfile, wearablesProvider, webBrowser, webRequestController, decentralandUrlsSource, profileChangesBus)
176+
new LobbyForNewAccountAuthState(fsm, viewInstance, this, CurrentState, characterPreviewController, selfProfile, wearablesProvider, webBrowser, webRequestController, decentralandUrlsSource, profileChangesBus, referrer)
174177
);
175178

176179
if (enableEmailOTP)

Explorer/Assets/DCL/AuthenticationScreenFlow/States/LobbyForNewAccountAuthState.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using DCL.Profiles.Self;
1313
using DCL.UI;
1414
using DCL.Utilities;
15+
using DCL.Web3;
1516
using DCL.WebRequests;
1617
using MVC;
1718
using System;
@@ -37,6 +38,7 @@ public class LobbyForNewAccountAuthState : AuthStateBase, IPayloadedState<(Profi
3738
private readonly IWebRequestController webRequestController;
3839
private readonly IDecentralandUrlsSource decentralandUrlsSource;
3940
private readonly ProfileChangesBus profileChangesBus;
41+
private readonly Web3Address? referrer;
4042

4143
private readonly AvatarRandomizer avatarRandomizer = new ();
4244

@@ -60,7 +62,8 @@ public LobbyForNewAccountAuthState(MVCStateMachine<AuthStateBase> fsm,
6062
UnityAppWebBrowser webBrowser,
6163
IWebRequestController webRequestController,
6264
IDecentralandUrlsSource decentralandUrlsSource,
63-
ProfileChangesBus profileChangesBus) : base(viewInstance)
65+
ProfileChangesBus profileChangesBus,
66+
string? referrer = null) : base(viewInstance)
6467
{
6568
view = viewInstance.LobbyForNewAccountAuthView;
6669

@@ -74,6 +77,9 @@ public LobbyForNewAccountAuthState(MVCStateMachine<AuthStateBase> fsm,
7477
this.webRequestController = webRequestController;
7578
this.decentralandUrlsSource = decentralandUrlsSource;
7679
this.profileChangesBus = profileChangesBus;
80+
// Normalized/validated once at construction so the field is always canonical;
81+
// an invalid launch-argument value degrades to "no referral tracking".
82+
this.referrer = Web3Address.FromUntrusted(referrer);
7783

7884
characterPreviewView = viewInstance.CharacterPreviewView;
7985
characterPreviewOrigPosition = characterPreviewView.transform.localPosition;
@@ -124,6 +130,9 @@ public void Enter((Profile profile, string email, bool isCached, CancellationTok
124130
view.TermsOfUseAndPrivacyLink.OnLinkClicked += OpenClickableURL;
125131

126132
UpdateFinalizeButtonState();
133+
134+
view.JumpInIcon.SetActive(true);
135+
view.FinalizeLoading.SetActive(false);
127136
}
128137

129138
public override void Exit()
@@ -276,6 +285,8 @@ private void FinalizeNewUser()
276285
{
277286
view.FinalizeNewUserButton.interactable = false;
278287
view.BackButton.interactable = false;
288+
view.JumpInIcon.SetActive(false);
289+
view.FinalizeLoading.SetActive(true);
279290

280291
if (view.SubscribeToggle.isOn && !string.IsNullOrEmpty(userEmail))
281292
SubscribeToNewsletterAsync(userEmail).Forget();
@@ -289,13 +300,20 @@ async UniTaskVoid PublishNewProfileAsync(CancellationToken ct)
289300
try
290301
{
291302
newUserProfile.Name = view.ProfileNameInputField.Text;
303+
292304
Profile? publishedProfile = await selfProfile.UpdateProfileAsync(newUserProfile, ct, updateAvatarInWorld: false);
293305
newUserProfile = publishedProfile ?? throw new ProfileNotFoundException();
294306

295307
// Notify profile-bus subscribers (sidebar thumbnail, explore panel, chat) that the
296308
// freshly created profile is live
297309
profileChangesBus.PushUpdate(newUserProfile);
298310

311+
// Register the referral here — awaited BEFORE the user proceeds to the world —
312+
// so the referral exists before the first LOGGED_IN event reaches the backend
313+
// (whose finalize step drops events for referrals that don't exist yet). Best
314+
// effort: a failed call must not fail onboarding.
315+
await RegisterReferralAsync(ct);
316+
299317
// Mark the analytics-visible end of the onboarding step. Anything between
300318
// LOGGED_IN (avatar customization shown) and PROFILE_FINALIZED is the user
301319
// setting up their account.
@@ -324,6 +342,49 @@ async UniTaskVoid PublishNewProfileAsync(CancellationToken ct)
324342
}
325343
}
326344

345+
/// <summary>
346+
/// Registers the referral: POST creates it, PATCH marks the invited user as signed up.
347+
/// Awaited by the caller so the create completes before the user enters the world, and
348+
/// best-effort so a failure never fails onboarding. Runs on the login-flow token, so
349+
/// abandoning the flow abandons the attribution too.
350+
/// </summary>
351+
private async UniTask RegisterReferralAsync(CancellationToken ct)
352+
{
353+
if (referrer == null)
354+
return;
355+
356+
try
357+
{
358+
string url = decentralandUrlsSource.Url(DecentralandUrl.ReferralProgress);
359+
360+
// referrer is validated at construction (Web3Address.FromUntrusted), safe to interpolate
361+
var jsonBody = $"{{\"referrer\":\"{referrer.Value}\"}}";
362+
363+
await webRequestController.SignedFetchPostAsync(
364+
new CommonArguments(URLAddress.FromString(url)),
365+
GenericPostArguments.CreateJson(jsonBody),
366+
string.Empty,
367+
ct)
368+
.WithNoOpAsync();
369+
370+
await webRequestController.SignedFetchPatchAsync(
371+
new CommonArguments(URLAddress.FromString(url)),
372+
GenericPostArguments.Empty,
373+
string.Empty,
374+
ct)
375+
.WithNoOpAsync();
376+
}
377+
catch (OperationCanceledException) { }
378+
catch (Exception e)
379+
{
380+
// Best-effort attribution: any failure (timeout, network) must not surface as a
381+
// Sentry error nor block onboarding. The POST is idempotent server-side (a same-
382+
// referrer duplicate returns 204, not an error), so a retry — here on re-entry, or
383+
// from a future login-time retry using the launcher-persisted referrer — is safe.
384+
ReportHub.LogWarning(ReportCategory.AUTHENTICATION, $"Referral registration failed: {e.Message}");
385+
}
386+
}
387+
327388
private async UniTaskVoid SubscribeToNewsletterAsync(string email)
328389
{
329390
try
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using DCL.Web3;
2+
using DCL.Web3.Authenticators;
3+
using NUnit.Framework;
4+
5+
namespace DCL.AuthenticationScreenFlow.Tests
6+
{
7+
[TestFixture]
8+
public class DeepLinkSignInUrlShould
9+
{
10+
private const string BASE_URL = "https://decentraland.org/auth/requests";
11+
private const string REQUEST_ID = "req-1";
12+
private const string LOGIN_METHOD = "METAMASK";
13+
14+
[Test]
15+
public void BuildBaseUrlWithoutReferrer()
16+
{
17+
string url = DeepLinkSignInUrl.Build(BASE_URL, REQUEST_ID, LOGIN_METHOD, bridgeOnly: false, referrer: null);
18+
19+
Assert.AreEqual($"{BASE_URL}/{REQUEST_ID}?loginMethod={LOGIN_METHOD}&flow=deeplink", url);
20+
}
21+
22+
[Test]
23+
public void AppendBridgeOnlyFlag()
24+
{
25+
string url = DeepLinkSignInUrl.Build(BASE_URL, REQUEST_ID, LOGIN_METHOD, bridgeOnly: true, referrer: null);
26+
27+
Assert.AreEqual($"{BASE_URL}/{REQUEST_ID}?loginMethod={LOGIN_METHOD}&flow=deeplink&bridgeOnly", url);
28+
}
29+
30+
[Test]
31+
public void AppendLowercasedReferrerWhenValid()
32+
{
33+
string url = DeepLinkSignInUrl.Build(BASE_URL, REQUEST_ID, LOGIN_METHOD, bridgeOnly: false,
34+
Web3Address.FromUntrusted("0x24E5F44999C151F08609F8E27B2238C773C4D020"));
35+
36+
Assert.AreEqual(
37+
$"{BASE_URL}/{REQUEST_ID}?loginMethod={LOGIN_METHOD}&flow=deeplink&referrer=0x24e5f44999c151f08609f8e27b2238c773c4d020",
38+
url);
39+
}
40+
41+
[TestCase(null)]
42+
[TestCase("")]
43+
[TestCase("not-an-address")]
44+
[TestCase("0x123")]
45+
[TestCase("javascript:alert(1)")]
46+
public void OmitReferrerWhenInvalid(string? rawReferrer)
47+
{
48+
// FromUntrusted degrades every invalid value to null, matching how the
49+
// authenticator constructs the field.
50+
string url = DeepLinkSignInUrl.Build(BASE_URL, REQUEST_ID, LOGIN_METHOD, bridgeOnly: false, Web3Address.FromUntrusted(rawReferrer));
51+
52+
StringAssert.DoesNotContain("referrer", url);
53+
}
54+
55+
[Test]
56+
public void OmitReferrerWhenAddressWasBuiltUnvalidated()
57+
{
58+
// Defense-in-depth: even a Web3Address constructed directly from garbage
59+
// (the ctor does not validate) must not reach the URL.
60+
string url = DeepLinkSignInUrl.Build(BASE_URL, REQUEST_ID, LOGIN_METHOD, bridgeOnly: false, new Web3Address("not-an-address"));
61+
62+
StringAssert.DoesNotContain("referrer", url);
63+
}
64+
}
65+
}

Explorer/Assets/DCL/AuthenticationScreenFlow/Tests/DeepLinkSignInUrlShould.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)