Skip to content

Commit 74f1b81

Browse files
committed
feat(urls): configurable base domain via --base-domain
Target a non-decentraland.* deployment (e.g. interconnected.online) by resolving every backend host through DecentralandUrlsSource.ResolveDomain, which swaps the whole decentraland.{ENV} host token for a custom base domain supplied by the --base-domain app arg (AppArgsFlags.BASE_DOMAIN). The default path is byte-identical, swapping only the {ENV} TLD. Consumers that follow the resolved domain: teleport validation (ChatEnvironmentValidator), realm trust (DeepLinkAllowlist), the main-realm comms fallback (RealmController), the realm-name server list (RealmNamesMap), the pre-login feature-flag whitelist URL (GetFeatureFlagsUrl), the smart-wearable content fallback (SmartWearableCache), and the local scene adapter (LocalGateKeeperSceneAdapter). Gateway routing already follows it: the transform runs on the {ENV} template and ResolveDomain rewrites the whole token, gateway host included. The default "Empty place" no longer hardcodes a peer.decentraland.org thumbnail and falls back to the built-in placeholder. Characterization tests pin every DecentralandUrl across org/zone/custom domains and gateway routing (gatekeeper family included); unit tests cover custom-domain teleport validation and deep-link realm trust.
1 parent 18e08a3 commit 74f1b81

20 files changed

Lines changed: 444 additions & 37 deletions

Explorer/Assets/DCL/Chat/Commands/ChatEnvironmentValidator.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ namespace DCL.Chat.Commands
77
public class ChatEnvironmentValidator
88
{
99
private readonly DecentralandEnvironment dclEnvironment;
10+
private readonly string realmDomainSuffix;
1011

11-
public ChatEnvironmentValidator(DecentralandEnvironment dclEnvironment)
12+
public ChatEnvironmentValidator(DecentralandEnvironment dclEnvironment, string realmDomainSuffix)
1213
{
1314
this.dclEnvironment = dclEnvironment;
15+
this.realmDomainSuffix = realmDomainSuffix;
1416
}
1517

1618
public Result ValidateTeleport(string realmToTeleportTo)
@@ -21,12 +23,12 @@ public Result ValidateTeleport(string realmToTeleportTo)
2123
return Result.ErrorResult(
2224
"🔴 Error. You cannot change realms in the Today environment. Please restart DCL with the desired environment");
2325
case DecentralandEnvironment.Zone:
24-
return HostHasSuffix(realmToTeleportTo, IDecentralandUrlsSource.ZONE_DOMAIN)
26+
return HostHasSuffix(realmToTeleportTo, realmDomainSuffix)
2527
? Result.SuccessResult()
2628
: Result.ErrorResult(
2729
"🔴 Error. You cannot teleport to other realms that are not Zone in Zone environment. Please restart DCL with the desired environment");
2830
case DecentralandEnvironment.Org:
29-
return HostHasSuffix(realmToTeleportTo, IDecentralandUrlsSource.ORG_DOMAIN)
31+
return HostHasSuffix(realmToTeleportTo, realmDomainSuffix)
3032
? Result.SuccessResult()
3133
: Result.ErrorResult(
3234
"🔴 Error. You cannot teleport to other realms that are not Org or World in Org environment. Please restart DCL with the desired environment");

Explorer/Assets/DCL/Chat/Commands/Tests/ChatTeleporterShould.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public void SetUp()
4040
IScenesCache scenesCache = Substitute.For<IScenesCache>();
4141
scenesCache.CurrentParcel.Returns(currentParcel);
4242

43-
chatTeleporter = new ChatTeleporter(realmNavigator, new ChatEnvironmentValidator(DecentralandEnvironment.Org), urlsSource, scenesCache);
43+
chatTeleporter = new ChatTeleporter(realmNavigator, new ChatEnvironmentValidator(DecentralandEnvironment.Org, IDecentralandUrlsSource.ORG_DOMAIN), urlsSource, scenesCache);
4444
}
4545

4646
[Test]

Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public static class AppArgsFlags
2222
public const string REALM = "realm";
2323
public const string COMMS_ADAPTER = "comms-adapter";
2424
public const string GATEKEEPER_URL = "gatekeeper-url";
25+
public const string BASE_DOMAIN = "base-domain";
2526
public const string LOCAL_SCENE = "local-scene";
2627
public const string POSITION = "position";
2728
public const string SPAWN_POINT = "spawnpoint";

Explorer/Assets/DCL/Infrastructure/Global/AppArgs/DeepLinkAllowlist.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ public static class DeepLinkAllowlist
165165
// means loopback-only — the safe default when feature flags are unavailable (e.g. before they are fetched).
166166
private static HashSet<string> whitelistedWorlds = new();
167167

168+
// Extra first-party base domain (set from --base-domain), trusted for realm hosting exactly like the
169+
// decentraland.* domains. Null = decentraland-only.
170+
private static string? customBaseDomain;
171+
172+
public static void SetCustomBaseDomain(string? domain) =>
173+
customBaseDomain = string.IsNullOrWhiteSpace(domain) ? null : domain.Trim();
174+
168175
public static bool IsPermitted(string key) =>
169176
PERMITTED_KEYS.Contains(key);
170177

@@ -221,6 +228,12 @@ public static bool IsRealmWhitelisted(string? realm)
221228
// is what rejects lookalikes such as "decentraland.org.attacker.com" and "evil-decentraland.org".
222229
private static bool IsDecentralandHost(string host)
223230
{
231+
if (customBaseDomain != null
232+
&& host.Length > customBaseDomain.Length
233+
&& host[host.Length - customBaseDomain.Length - 1] == '.'
234+
&& host.EndsWith(customBaseDomain, StringComparison.OrdinalIgnoreCase))
235+
return true;
236+
224237
// Indexed loop, not foreach: enumerating the IReadOnlyList would allocate an enumerator.
225238
IReadOnlyList<string> domains = IDecentralandUrlsSource.ALL_DOMAINS;
226239

Explorer/Assets/DCL/Infrastructure/Global/AppArgs/Tests/AppArgsTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public void TearDown()
1010
{
1111
// Reset the cached/overridden world whitelist so tests don't leak state into one another.
1212
DeepLinkAllowlist.SetWhitelistedWorlds(null);
13+
DeepLinkAllowlist.SetCustomBaseDomain(null);
1314
}
1415

1516
[Test]
@@ -281,6 +282,19 @@ public void ClassifyRealmAsWhitelisted(string realm, bool expected)
281282
Assert.AreEqual(expected, DeepLinkAllowlist.IsRealmWhitelisted(realm));
282283
}
283284

285+
[Test]
286+
public void TrustCustomBaseDomainHostForWhitelistedRealm()
287+
{
288+
DeepLinkAllowlist.SetWhitelistedWorlds(new[] { "test-world.dcl.eth" });
289+
const string REALM = "https://worlds-content-server.interconnected.online/world/test-world.dcl.eth";
290+
291+
DeepLinkAllowlist.SetCustomBaseDomain(null);
292+
Assert.IsFalse(DeepLinkAllowlist.IsRealmWhitelisted(REALM), "a non-decentraland host is untrusted without --base-domain");
293+
294+
DeepLinkAllowlist.SetCustomBaseDomain("interconnected.online");
295+
Assert.IsTrue(DeepLinkAllowlist.IsRealmWhitelisted(REALM), "the --base-domain host is trusted like decentraland.* once set");
296+
}
297+
284298
[Test]
285299
public void DeferDeepLinkUntilInitializeDeepLinksIsCalled()
286300
{

Explorer/Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ await bootstrapContainer.InitializeContainerAsync<BootstrapContainer, BootstrapS
133133
var cdpClient = ChromeDevToolHandler.New(applicationParametersParser.HasFlag(AppArgsFlags.LAUNCH_CDP_MONITOR_ON_START));
134134
WebRequestsContainer? webRequestsContainer = await WebRequestsContainer.CreateAsync(settingsContainer, identityCache, debugContainer.Builder, decentralandUrlsSource, cdpClient, container.DiagnosticsContainer.SentrySampler, container.RealmClock, ct);
135135
container.WebRequestsContainer = webRequestsContainer;
136-
var realmUrls = new RealmUrls(realmLaunchSettings, new RealmNamesMap(webRequestsContainer.WebRequestController), decentralandUrlsSource);
136+
var realmUrls = new RealmUrls(realmLaunchSettings, new RealmNamesMap(webRequestsContainer.WebRequestController, decentralandUrlsSource), decentralandUrlsSource);
137137

138138
container.Bootstrap = await CreateBootstrapperAsync(debugSettings, debugContainer, applicationParametersParser, splashScreen, realmUrls, diskCache, partialsDiskCache, container, webRequestsContainer, settingsContainer, realmLaunchSettings, world, container.settings.BuildData, dclVersion, ct);
139139
container.CompositeWeb3Provider = CreateWeb3Dependencies(sceneLoaderSettings, web3AccountFactory, identityCache, browser, container.Analytics, decentralandUrlsSource, decentralandEnvironment, applicationParametersParser, webRequestsContainer.WebRequestController, container.DeeplinkSigninIdentityId, container.DeeplinkLoginAwaitingSigninRequestId);

Explorer/Assets/DCL/Infrastructure/Global/Dynamic/ChatContainer.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ public static ChatContainer Create(
103103
var chatHistory = new ChatHistory();
104104
var chatEventBus = new ChatEventBus();
105105

106-
var chatTeleporter = new ChatTeleporter(realmNavigator, new ChatEnvironmentValidator(bootstrapContainer.Environment), bootstrapContainer.DecentralandUrlsSource, staticContainer.ScenesCache);
106+
string realmDomainSuffix = bootstrapContainer.DecentralandUrlsSource
107+
.Url(DCL.Multiplayer.Connections.DecentralandUrls.DecentralandUrl.Host)
108+
.Replace("https://", string.Empty);
109+
var chatTeleporter = new ChatTeleporter(realmNavigator, new ChatEnvironmentValidator(bootstrapContainer.Environment, realmDomainSuffix), bootstrapContainer.DecentralandUrlsSource, staticContainer.ScenesCache);
107110

108111
var reloadSceneChatCommand = new ReloadSceneChatCommand(reloadSceneController, globalWorld, playerEntity, staticContainer.ScenesCache, teleportController, localSceneDevelopment);
109112

Explorer/Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ private async UniTask InitializeFlowAsync(CancellationToken ct)
257257

258258
applicationParametersParser.TryGetValue(AppArgsFlags.GATEKEEPER_URL, out string? cliGatekeeperUrl);
259259
applicationParametersParser.TryGetValue(AppArgsFlags.OPTIMIZED_ASSETS_URL, out string? cliOptimizedAssetsUrl);
260+
applicationParametersParser.TryGetValue(AppArgsFlags.BASE_DOMAIN, out string? cliBaseDomain);
261+
DeepLinkAllowlist.SetCustomBaseDomain(cliBaseDomain);
260262

261263
if (string.IsNullOrEmpty(cliOptimizedAssetsUrl) && launchSettings.useLocalAssetBundles)
262264
cliOptimizedAssetsUrl = launchSettings.LocalAssetBundlesBaseUrl();
@@ -268,7 +270,8 @@ private async UniTask InitializeFlowAsync(CancellationToken ct)
268270
debugSettings.GatekeeperMode,
269271
debugSettings.CustomGatekeeperUrl,
270272
cliGatekeeperUrl,
271-
cliOptimizedAssetsUrl);
273+
cliOptimizedAssetsUrl,
274+
cliBaseDomain);
272275
DiagnosticInfoUtils.LogEnvironment(decentralandUrlsSource);
273276

274277
splashScreen = await assetsProvisioner.ProvideInstanceAsync(splashScreenRef, ct: ct);
@@ -538,9 +541,10 @@ private bool ShouldForceSingleRunningInstance(IAppArgs appArgs)
538541
private async UniTask InitializeDeepLinkWorldWhitelistAsync(IAppArgs appArgs, CancellationToken ct)
539542
{
540543
appArgs.TryGetValue(AppArgsFlags.FeatureFlags.URL, out string? featureFlagsOverride);
544+
appArgs.TryGetValue(AppArgsFlags.BASE_DOMAIN, out string? cliBaseDomain);
541545

542546
string featureFlagsBase = string.IsNullOrEmpty(featureFlagsOverride)
543-
? DecentralandUrlsSource.GetFeatureFlagsUrl(decentralandEnvironment)
547+
? DecentralandUrlsSource.GetFeatureFlagsUrl(decentralandEnvironment, cliBaseDomain)
544548
: featureFlagsOverride.TrimEnd('/');
545549

546550
IReadOnlyList<string> whitelistedWorlds = await DeepLinkWorldWhitelistProvider.FetchAsync($"{featureFlagsBase}/{FeatureFlagOptions.APP_NAME}.json", ct);

Explorer/Assets/DCL/Infrastructure/Global/Dynamic/RealmController.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,9 +371,8 @@ private string ResolveHostname(URLDomain realm, ServerAbout about)
371371
else
372372
hostname = about.comms == null
373373

374-
// Consider it as the "main" realm which shares the comms with many catalysts
375-
// TODO: take in consideration the web3-network. If its sepolia then it should be .zone
376-
? "realm-provider." + IDecentralandUrlsSource.ORG_DOMAIN
374+
// Consider it as the "main" realm which shares the comms with many catalysts.
375+
? "realm-provider." + decentralandUrlsSource.Url(DecentralandUrl.Host).Replace("https://", string.Empty)
377376
: new Uri(realm.Value).Host;
378377

379378
return hostname;

Explorer/Assets/DCL/Infrastructure/Global/Dynamic/RealmUrl/Names/RealmNamesMap.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Cysharp.Threading.Tasks;
22
using DCL.Diagnostics;
3+
using DCL.Multiplayer.Connections.DecentralandUrls;
34
using DCL.Utilities.Extensions;
45
using DCL.WebRequests;
56
using System;
@@ -11,12 +12,14 @@ namespace Global.Dynamic.RealmUrl.Names
1112
public class RealmNamesMap : IRealmNamesMap
1213
{
1314
private readonly IWebRequestController webRequestController;
15+
private readonly IDecentralandUrlsSource decentralandUrlsSource;
1416
private readonly Dictionary<string, string> cachedUrlToNameDictionary = new ();
1517
private IReadOnlyList<NodeDTO>? cachedNodes;
1618

17-
public RealmNamesMap(IWebRequestController webRequestController)
19+
public RealmNamesMap(IWebRequestController webRequestController, IDecentralandUrlsSource decentralandUrlsSource)
1820
{
1921
this.webRequestController = webRequestController;
22+
this.decentralandUrlsSource = decentralandUrlsSource;
2023
}
2124

2225
public async UniTask<string> UrlFromNameAsync(string name, CancellationToken token)
@@ -38,7 +41,7 @@ private async UniTask<IReadOnlyList<NodeDTO>> NodesAsync(CancellationToken token
3841
{
3942
if (cachedNodes == null)
4043
{
41-
CommonArguments arguments = "https://peer.decentraland.org/lambdas/contracts/servers";
44+
CommonArguments arguments = decentralandUrlsSource.Url(DecentralandUrl.Servers);
4245

4346
cachedNodes = await webRequestController
4447
.GetAsync(arguments, token, ReportCategory.GENERIC_WEB_REQUEST)

0 commit comments

Comments
 (0)