Editor/Core/RelaySetupRules.cs
using System;
namespace TeamCreate;
/// <summary>
/// The relay this computer set up for itself (the dock's Set up relay button). It is per machine and lives in <c>.collaboration/relay-url.txt</c>, which Git
/// ignores and the project manifest skips, because the shared <c>ProjectSettings/collaboration.json</c> is hashed into the manifest: writing the address there
/// would make the host's project differ from the friend's. The friend needs no copy of it; the invite code carries the relay's address.
/// </summary>
public static class LocalRelayFile
{
public const string FileName = "relay-url.txt";
/// <summary>The address on the first line that is not blank and not a comment, or null when there is none or it is unusable.</summary>
public static string Parse(string text, out string error)
{
error = null;
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
foreach (string raw in text.Split('\n'))
{
string line = raw.Trim();
if (line.Length == 0 || line[0] == '#')
{
continue;
}
if (!RelayProtocol.TryValidateUrl(line, out _, out string reason))
{
error = "The relay address in " + FileName + " is not usable: " + reason;
return null;
}
return line;
}
return null;
}
public static string Format(string relayUrl)
{
return "# The relay this computer set up for itself. Delete this file to go back to the library's default relay.\n" + relayUrl.Trim() + "\n";
}
}
public enum RelaySetupMode
{
/// <summary>The library's own relay is in use.</summary>
Shared,
/// <summary>The relay this computer set up is in use.</summary>
Own,
/// <summary>The project's settings name the relay.</summary>
Project,
/// <summary>The project's settings turn relaying off.</summary>
Off,
/// <summary>No relay at all: the case the setup button exists for.</summary>
Missing,
/// <summary>A relay that only this computer can reach.</summary>
LocalOnly,
/// <summary>A setup is running.</summary>
Working,
/// <summary>A setup just finished and its relay is in use.</summary>
Done,
/// <summary>The last setup failed and no relay is in use.</summary>
Failed
}
public sealed record RelaySetupInput(string ProjectRelay, string LocalRelay, string BuiltInRelay, bool Working, string Progress, DeployResult LastResult);
/// <summary>What the dock's relay card shows: one sentence, which buttons make sense, and whether the sentence reports a problem.</summary>
public sealed record RelaySetupView(RelaySetupMode Mode, string Text, bool ShowOpen, bool ShowSetup, bool ShowCancel, bool IsProblem);
public static class RelaySetupRules
{
public const string ClipboardHint = "Step 1 opens Cloudflare, where you create a token in one click. Step 2 reads it from the clipboard and sets the relay up.";
public static RelaySetupView Describe(RelaySetupInput input)
{
if (input.Working)
{
return new RelaySetupView(RelaySetupMode.Working, string.IsNullOrWhiteSpace(input.Progress) ? "Setting up the relay…" : input.Progress, false, false, true, false);
}
string trimmedProject = input.ProjectRelay?.Trim();
if (string.Equals(trimmedProject, RelayDefaults.Off, StringComparison.OrdinalIgnoreCase))
{
return new RelaySetupView(RelaySetupMode.Off, "Relaying is turned off in this project's settings (RelayUrl is \"off\"), so friends can join only from your network or through a forwarded port.", false, false, false, false);
}
bool projectNamesRelay = !string.IsNullOrWhiteSpace(trimmedProject);
string effective = RelayDefaults.Resolve(input.ProjectRelay, Fallback(input.LocalRelay, input.BuiltInRelay));
bool ownIsEffective = !projectNamesRelay && !string.IsNullOrWhiteSpace(input.LocalRelay);
if (effective != null && !RelayProtocol.IsPublicRelay(effective))
{
string where = projectNamesRelay ? "in this project's settings" : "set up on this computer";
return new RelaySetupView(RelaySetupMode.LocalOnly, "The relay " + where + " is on this computer, so only this computer can use it and a friend on another network cannot join. Use a public relay address" + (projectNamesRelay ? " in ProjectSettings/collaboration.json." : "."), false, !projectNamesRelay, false, true);
}
if (effective != null)
{
string host = HostOf(effective);
if (input.LastResult != null && input.LastResult.Ok && string.Equals(input.LastResult.RelayUrl, effective, StringComparison.Ordinal) && ownIsEffective)
{
return new RelaySetupView(RelaySetupMode.Done, "Relay ready: " + host + ". Every session you host now uses it, and friends on any network can join.", false, false, false, false);
}
if (projectNamesRelay)
{
return new RelaySetupView(RelaySetupMode.Project, "Relay: " + host + " (named in this project's settings). Friends on any network can join.", false, false, false, false);
}
if (ownIsEffective)
{
return new RelaySetupView(RelaySetupMode.Own, "Relay: " + host + " (set up on this computer). Friends on any network can join.", false, false, false, false);
}
return new RelaySetupView(RelaySetupMode.Shared, "Relay: " + host + " (the library's shared relay). Friends on any network can join.", false, false, false, false);
}
if (input.LastResult != null && !input.LastResult.Ok && !input.LastResult.Cancelled && !string.IsNullOrWhiteSpace(input.LastResult.Error))
{
return new RelaySetupView(RelaySetupMode.Failed, input.LastResult.Error, true, true, false, true);
}
string lead = input.LastResult != null && input.LastResult.Cancelled ? "Setup was cancelled, and no relay is set up. " : "No relay is set up, so friends on other networks cannot join. ";
return new RelaySetupView(RelaySetupMode.Missing, lead + ClipboardHint, true, true, false, false);
}
/// <summary>The relay used when the project names none: the one this computer set up, else the library's own. The session and the card both ask here, so they cannot disagree.</summary>
public static string Fallback(string localRelay, string builtInRelay)
{
return !string.IsNullOrWhiteSpace(localRelay) ? localRelay.Trim() : (string.IsNullOrWhiteSpace(builtInRelay) ? null : builtInRelay.Trim());
}
public static string HostOf(string relayUrl)
{
return Uri.TryCreate(relayUrl?.Trim(), UriKind.Absolute, out Uri uri) ? uri.Host : relayUrl;
}
}