Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d4fe2ba
fix: evict poisoned smart-wearable cache entry on failed metadata fet…
decentraland-bot Aug 29, 2026
91b66df
fix: drop inbound direct messages when the Friends feature is disable…
decentraland-bot Aug 29, 2026
2c1ac8e
fix: report Segment external flush failures as warnings, not Sentry e…
decentraland-bot Aug 29, 2026
2b132ec
fix: fail fast with an error on insecure non-loopback realms instead …
decentraland-bot Aug 29, 2026
53841a4
fix: return a JSON-RPC error for disallowed scene Web3 methods instea…
decentraland-bot Aug 29, 2026
f005dda
fix: default the Places tab to Most Active so live-user scenes surfac…
decentraland-bot Aug 29, 2026
3ab182a
Revert "fix: fail fast with an error on insecure non-loopback realms …
lorenzo-ranciaffi Aug 31, 2026
c04d90f
Revert "fix: report Segment external flush failures as warnings, not …
lorenzo-ranciaffi Aug 31, 2026
45b40b8
Revert "fix: drop inbound direct messages when the Friends feature is…
lorenzo-ranciaffi Aug 31, 2026
b0eabda
fix: keep the LiveKit chat room disconnected in local scene developme…
lorenzo-ranciaffi Aug 31, 2026
83d28b0
fix: keep a scene.json-less smart wearable smart, log and evict inste…
lorenzo-ranciaffi Aug 31, 2026
edad8bd
refactor: move the JSON-RPC method-not-found code to a const (#9833)
lorenzo-ranciaffi Aug 31, 2026
3853c9e
refactor: condense PR comments to single-line why-comments
lorenzo-ranciaffi Aug 31, 2026
00399b4
reduce lint errors
lorenzo-ranciaffi Aug 31, 2026
aa3e205
feat: surface analytics disk-full errors and warn the user with a popup
lorenzo-ranciaffi Sep 1, 2026
2b7227e
Merge branch 'dev' into fix/bugsweep-week-2026-w34
lorenzo-ranciaffi Sep 1, 2026
c1e6551
Merge branch 'dev' into fix/bugsweep-week-2026-w34
lorenzo-ranciaffi Sep 2, 2026
ef18064
fix: make the disk-full popup reliable and stop exception spam for it
lorenzo-ranciaffi Sep 2, 2026
d03ca1c
moved diskfull error in the bridge
lorenzo-ranciaffi Sep 2, 2026
14fef20
win rust bridge compiled
lorenzo-ranciaffi Sep 2, 2026
ca6b7ba
Merge branch 'dev' into fix/bugsweep-week-2026-w34
lorenzo-ranciaffi Sep 2, 2026
1670ba9
refined comments
lorenzo-ranciaffi Sep 2, 2026
04f89fe
Merge branch 'dev' into fix/bugsweep-week-2026-w34
lorenzo-ranciaffi Sep 2, 2026
e095044
code review
lorenzo-ranciaffi Sep 2, 2026
ea239d6
code review
lorenzo-ranciaffi Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
using Cysharp.Threading.Tasks;
using DCL.AvatarRendering.Loading.Components;
using DCL.AvatarRendering.Thumbnails.Utils;
using DCL.AvatarRendering.Wearables.Components;
using DCL.Backpack;
using DCL.Ipfs;
using JetBrains.Annotations;
using MVC;
using System.Threading;
using UnityEngine;

namespace Runtime.Wearables
{
Expand All @@ -19,7 +15,7 @@ public class SmartWearableAuthorizationPopupController : ControllerBase<SmartWe
private readonly NftTypeIconSO categoryIcons;

public SmartWearableAuthorizationPopupController(
[NotNull] ViewFactoryMethod viewFactory,
ViewFactoryMethod viewFactory,
SmartWearableCache smartWearableCache,
NftTypeIconSO rarityBackgrounds,
NFTColorsSO rarityColors,
Expand Down Expand Up @@ -59,20 +55,24 @@ protected override void OnViewShow()
base.OnViewShow();

var wearable = inputData.Wearable;
var thumbnail = ((IAvatarAttachment)wearable).ThumbnailAssetResult?.Asset.Sprite;
var thumbnail = wearable.ThumbnailAssetResult?.Asset.Sprite;
var rarityBackground = rarityBackgrounds.GetTypeImage(wearable.GetRarity());
var rarityColor = rarityColors.GetColor(wearable.GetRarity());
var categoryIcon = categoryIcons.GetTypeImage(wearable.GetCategory());
viewInstance.Setup(wearable.GetName(), thumbnail, rarityBackground, rarityColor, categoryIcon);
viewInstance!.Setup(wearable.GetName(), thumbnail, rarityBackground, rarityColor, categoryIcon);
Comment thread
lorenzo-ranciaffi marked this conversation as resolved.

UpdatePermissionsAsync(wearable).Forget();
}

private async UniTask UpdatePermissionsAsync(IWearable wearable)
{
(_, SceneMetadata sceneMetadata) = await smartWearableCache.GetCachedSceneInfoAsync(wearable, CancellationToken.None);
(_, SceneMetadata? sceneMetadata) = await smartWearableCache.GetCachedSceneInfoAsync(wearable, CancellationToken.None);

if (sceneMetadata == null)
return;

await UniTask.SwitchToMainThread();
viewInstance.SetPermissions(sceneMetadata.requiredPermissions);
viewInstance!.SetPermissions(sceneMetadata.requiredPermissions);
}

public static async UniTask<bool> RequestAuthorizationAsync(IMVCManager mvcManager, IWearable wearable, CancellationToken ct)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async UniTask WaitChoiceAsync()
await UniTask.WhenAny(AuthorizeButton.OnClickAsync(), DenyButton.OnClickAsync());
}

public void Setup(string wearableName, Sprite thumbnail, Sprite rarityBackground, Color rarityColor, Sprite categoryIcon)
public void Setup(string wearableName, Sprite? thumbnail, Sprite rarityBackground, Color rarityColor, Sprite categoryIcon)
{
PromptText.text = string.Format(PromptFormat, wearableName);
WearableThumbnail.sprite = thumbnail;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Cysharp.Threading.Tasks;
using DCL.Chat.History;
using DCL.Diagnostics;
using DCL.FeatureFlags;
using DCL.Friends;
using DCL.Friends.UserBlocking;
using DCL.Multiplayer.Connections.RoomHubs;
Expand Down Expand Up @@ -113,6 +114,10 @@ public async UniTask InitializeAsync(CancellationToken ct)
{
await rpcChatPrivacyService.GetOwnSocialSettingsAsync(cts.Token);

// The chat room is never connected in local scene development (see CommsContainer); waiting would just burn the timeout.
if (FeaturesRegistry.Instance.IsEnabled(FeatureId.LocalSceneDevelopment))
return;

await UniTask.WaitUntil(() =>
chatRoom.Info.ConnectionState == LKConnectionState.ConnConnected, cancellationToken: cts.Token)
.Timeout(TimeSpan.FromMinutes(TIMEOUT_FRIENDS_CONTAINER_MINUTES));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ public static CommsContainer Create(
roomHub = new RoomHub(
localSceneDevelopment ? IConnectiveRoom.Null.INSTANCE : archipelagoIslandRoom,
gateKeeperSceneRoom,
chatRoom,

// Friends is disabled in local scene development, so DMs delivered over the chat room would be unanswerable
localSceneDevelopment ? IConnectiveRoom.Null.INSTANCE : chatRoom,
voiceChatRoom
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,8 @@ await MapRendererContainer
uiShellContainer.MvcManager,
bootstrapContainer.DecentralandUrlsSource));

globalPlugins.Add(new AnalyticsDiskFullPopupPlugin(bootstrapContainer.Analytics.EventBus, uiShellContainer.MvcManager));

// ReSharper disable once MethodHasAsyncOverloadWithCancellation
if (FeaturesRegistry.Instance.IsEnabled(FeatureId.VoiceChat))
globalPlugins.Add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ namespace SceneRuntime.Apis.Modules.Ethereums
{
public class EthereumApiWrapper : JsApiWrapper
{
// JSON-RPC 2.0 "Method not found": https://www.jsonrpc.org/specification#error_object
private const long JSON_RPC_METHOD_NOT_FOUND = -32601;

private readonly IEthereumApi ethereumApi;
private readonly ISceneExceptionsHandler sceneExceptionsHandler;
private readonly IWeb3IdentityCache web3IdentityCache;
Expand Down Expand Up @@ -76,7 +79,7 @@ async UniTask<SignMessageResponse> RequestPersonalSignatureAsync(CancellationTok
},
}, Web3RequestSource.SdkScene, ct);

return new SignMessageResponse(hex, message, (string)response.result);
return new SignMessageResponse(hex, message, (string?)response.result ?? string.Empty);
}
catch (Exception e)
{
Expand All @@ -91,10 +94,10 @@ async UniTask<SignMessageResponse> RequestPersonalSignatureAsync(CancellationTok
[PublicAPI("Used by StreamingAssets/Js/Modules/EthereumController.js")]
public object SendAsync(double id, string method, string jsonParams)
{
return SendAndFormatAsync(id, method, JsonConvert.DeserializeObject<object[]>(jsonParams) ?? Array.Empty<object>(), sendCancellationToken.Token)
return SendAndFormatAsync(JsonConvert.DeserializeObject<object[]>(jsonParams) ?? Array.Empty<object>(), sendCancellationToken.Token)
.ToDisconnectedPromise(this);

async UniTask<SendEthereumMessageResponse> SendAndFormatAsync(double id, string method, object[] @params, CancellationToken ct)
async UniTask<SendEthereumMessageResponse> SendAndFormatAsync(object[] @params, CancellationToken ct)
{
try
{
Expand All @@ -110,6 +113,24 @@ async UniTask<SendEthereumMessageResponse> SendAndFormatAsync(double id, string
jsonAnyResponse = JsonConvert.SerializeObject(result),
};
}
catch (Web3MethodNotAllowedException e)
{
// Not an engine fault: OnEngineException would report it to Sentry and can suspend the scene on repeated calls
return new SendEthereumMessageResponse
{
jsonAnyResponse = JsonConvert.SerializeObject(new EthApiResponse
{
id = (long)id,
jsonrpc = "2.0",
result = null,
error = new EthApiError
{
code = JSON_RPC_METHOD_NOT_FOUND,
message = e.Message,
},
}),
};
}
catch (Exception e)
{
sceneExceptionsHandler.OnEngineException(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Utility
{
/// <summary>Lives in Utility rather than DCL.Analytics so UI-layer subscribers need no dependency on the analytics assembly.</summary>
public readonly struct AnalyticsDiskFullDetected { }
Comment thread
lorenzo-ranciaffi marked this conversation as resolved.
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System;
using System.Threading;
using UnityEngine;
using Utility;

namespace DCL.PerformanceAndDiagnostics.Analytics
{
Expand All @@ -25,6 +26,9 @@ public class AnalyticsContainer : DCLGlobalContainer<AnalyticsContainer.Settings

public EntitiesAnalytics EntitiesAnalytics { get; private set; } = null!;

/// <summary>Decouples analytics-originated events from their UI subscribers, which are created long after this container.</summary>
public IEventBus EventBus { get; } = new EventBus(invokeSubscribersOnMainThread: true);

public static async UniTask<AnalyticsContainer> CreateAsync(
IAppArgs appArgs,
IWeb3IdentityCache identityCache,
Expand All @@ -48,6 +52,7 @@ await container.InitializeContainerAsync<AnalyticsContainer, Settings>(settingsC
launcherTraits,
appArgs,
realmLaunchSettings.CurrentMode is LaunchMode.LocalSceneDevelopment,
container.EventBus,
ct);

var analyticsController = new AnalyticsController(service, appArgs, container.settings.AnalyticsConfig, launcherTraits, installSource, dclVersion, identityCache?.Identity);
Expand All @@ -66,26 +71,26 @@ await container.InitializeContainerAsync<AnalyticsContainer, Settings>(settingsC
return container;
}

private static IAnalyticsService CreateAnalyticsService(AnalyticsConfiguration analyticsConfig, LauncherTraits launcherTraits, IAppArgs args, bool isLocalSceneDevelopment, CancellationToken token)
private static IAnalyticsService CreateAnalyticsService(AnalyticsConfiguration analyticsConfig, LauncherTraits launcherTraits, IAppArgs args, bool isLocalSceneDevelopment, IEventBus eventBus, CancellationToken token)
{
// Avoid Segment analytics for: Unity Editor or Debug Mode (except when in Local Scene Development mode)

if (!Application.isEditor && (!args.HasDebugFlag() || isLocalSceneDevelopment))
return CreateSegmentAnalyticsOrFallbackToDebug(analyticsConfig, launcherTraits, token);
return CreateSegmentAnalyticsOrFallbackToDebug(analyticsConfig, launcherTraits, eventBus, token);

return analyticsConfig.Mode switch
{
AnalyticsMode.Segment => CreateSegmentAnalyticsOrFallbackToDebug(analyticsConfig, launcherTraits, token),
AnalyticsMode.Segment => CreateSegmentAnalyticsOrFallbackToDebug(analyticsConfig, launcherTraits, eventBus, token),
AnalyticsMode.DebugLog => new DebugAnalyticsService(),
AnalyticsMode.Disabled => throw new InvalidOperationException("Trying to create analytics when it is disabled"),
_ => throw new ArgumentOutOfRangeException(),
};
}

private static IAnalyticsService CreateSegmentAnalyticsOrFallbackToDebug(AnalyticsConfiguration analyticsConfig, LauncherTraits launcherTraits, CancellationToken token)
private static IAnalyticsService CreateSegmentAnalyticsOrFallbackToDebug(AnalyticsConfiguration analyticsConfig, LauncherTraits launcherTraits, IEventBus eventBus, CancellationToken token)
{
if (analyticsConfig.TryGetSegmentConfiguration(out Configuration segmentConfiguration))
return new RustSegmentAnalyticsService(segmentConfiguration.WriteKey!, launcherTraits.LauncherAnonymousId)
return new RustSegmentAnalyticsService(segmentConfiguration.WriteKey!, launcherTraits.LauncherAnonymousId, eventBus)
.WithTimeFlush(TimeSpan.FromSeconds(analyticsConfig.FlushInterval), token);

// Fall back to debug if segment is not configured
Expand Down
1 change: 1 addition & 0 deletions Explorer/Assets/DCL/Places/PlacesFilterSelectorView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public void ResetFilters(bool invokeEvents = true)
{
sortContentDeselectable.gameObject.SetActive(true);

// The default sort toggle must match PlacesView.ResetCurrentFilters
if (invokeEvents)
{
sortByMostActive.isOn = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Arch.SystemGroups;
using Cysharp.Threading.Tasks;
using DCL.Diagnostics;
using DCL.UI;
using DCL.UI.ErrorPopup;
using DCL.Utilities.Extensions;
using DCL.Utility.Types;
using MVC;
using System;
using System.Threading;
using UnityEngine;
using Utility;

namespace DCL.PluginSystem.Global
{
public class AnalyticsDiskFullPopupPlugin : IDCLGlobalPluginWithoutSettings
{
private readonly IEventBus analyticsEventBus;
private readonly IMVCManager mvcManager;
private readonly CancellationTokenSource cts = new ();
private IDisposable? subscription;

// The event repeats with every failing flush; the popup must show once per session
private bool popupShown;

public AnalyticsDiskFullPopupPlugin(IEventBus analyticsEventBus, IMVCManager mvcManager)
{
this.analyticsEventBus = analyticsEventBus;
this.mvcManager = mvcManager;
}

public void InjectToWorld(ref ArchSystemsWorldBuilder<Arch.Core.World> builder, in GlobalPluginArguments arguments) { }

public UniTask InitializeAsync(NoExposedPluginSettings settings, CancellationToken ct)
{
subscription = analyticsEventBus.Subscribe<AnalyticsDiskFullDetected>(OnDiskFull);
return UniTask.CompletedTask;
}

private void OnDiskFull(AnalyticsDiskFullDetected evt)
{
if (popupShown)
return;

popupShown = true;
ShowPopupAsync(cts.Token).Forget();
}

private async UniTaskVoid ShowPopupAsync(CancellationToken ct)
{
var data = new ErrorPopupData(
UIProperty<Sprite>.UseDefault,
UIProperty<string>.From("Storage Full"),
UIProperty<string>.From("Your device is running out of disk space. Free up space to keep Decentraland working correctly."));

EnumResult<TaskError> result = await mvcManager.ShowAsync(new ShowCommand<ErrorPopupView, ErrorPopupData>(data), ct)
.SuppressToResultAsync(ReportCategory.ANALYTICS);

// A show that failed never reached the user, so the next disk-full event may retry
if (result.Error is { State: not TaskError.Cancelled })
popupShown = false;
}

public void Dispose()
{
subscription?.Dispose();
cts.SafeCancelAndDispose();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading