"me-web?" 0.7.0: the name in quotes, copy buttons, and every translation rewritten in plain words
- The name is now always written in quotation marks, each language with its own: “me-web?”, «me-web?», „me-web?“, « me-web? », 「me-web?」. Without them the question mark read as the end of a sentence. In Arabic, Hebrew and the other right-to-left languages the question mark no longer jumps to the wrong side of the name. - Your addresses, your router, your network, the reverse name, the route and the code of a submission you sent now sit in a small box with a copy button, ready to paste into a message to your provider or anywhere else. - Every translation except English and Italian was rewritten for people who are not technical: everyday words first, the technical term in brackets, and one word for one thing across the whole app. They are still machine drafts; "Suggest a correction" is welcome in any language. - A few lines are plainer in English and Italian too: the test servers for calls, and the server in the middle that carries a call when two homes can't connect directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
d18caed157
commit
320936c688
260 files changed
+23662
-18072
No files matched your search
@@ -26,7 +26,6 @@ public class RunReport
|
||||
public DeviceReport DeviceReport { get; set; }
|
||||
public CellularReport Cellular { get; set; }
|
||||
public bool OwnNetwork { get; set; }
|
||||
public Shared.MeasuringSite Site { get; set; }
|
||||
public SpeedReport Speed { get; set; }
|
||||
public List<UseFinding> Uses { get; set; } = [];
|
||||
public HistorySummary History { get; set; }
|
||||
@@ -66,7 +65,6 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
|
||||
var online = server.Online(ct);
|
||||
var v4 = server.Echo(false, ct);
|
||||
var v6 = server.Echo(true, ct);
|
||||
var site = SpeedCheck.Nearest(ct);
|
||||
var route = Task.Run(async () =>
|
||||
{
|
||||
var target = await ServerProbe.ResolveV4(ct);
|
||||
@@ -167,7 +165,6 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
|
||||
var speed = Step(CheckId.Speed, async () =>
|
||||
{
|
||||
await Task.WhenAll(onlineDone, nat, behaviour, mail, names);
|
||||
run.Site = await site;
|
||||
if (run.Online?.State is not (OnlineState.Online or OnlineState.Rewritten))
|
||||
{
|
||||
Settle(new Finding { Id = CheckId.Speed, Outcome = Outcome.Unknown, Summary = "SpeedOffline" });
|
||||
@@ -178,8 +175,8 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
|
||||
Settle(new Finding { Id = CheckId.Speed, Outcome = Outcome.Unknown, Summary = "ReachOwnNetwork" });
|
||||
return;
|
||||
}
|
||||
run.Speed = await SpeedCheck.Run(https, run.Local?.Active?.Kind ?? LinkKind.Unknown, run.Site, ct);
|
||||
Settle(Verdicts.Speed(run.Speed, run.Site));
|
||||
run.Speed = await SpeedCheck.Run(https, run.Local?.Active?.Kind ?? LinkKind.Unknown, ct);
|
||||
Settle(Verdicts.Speed(run.Speed));
|
||||
});
|
||||
|
||||
await Step(CheckId.History, async () =>
|
||||
|
||||
@@ -49,8 +49,8 @@ public class Reach(ServerProbe server, Leftovers leftovers)
|
||||
var report = new ReachReport { Choice = choice };
|
||||
if (choice == ReachChoice.Skip) return report;
|
||||
|
||||
var ticket4 = run.PublicV4 is null ? (null, null) : await server.Ticket(run.Site ?? Endpoints.Main, false, ct);
|
||||
var ticket6 = run.PublicV6 is null ? (null, null) : await server.Ticket(run.Site ?? Endpoints.Main, true, ct);
|
||||
var ticket4 = run.PublicV4 is null ? (null, null) : await server.Ticket(false, ct);
|
||||
var ticket6 = run.PublicV6 is null ? (null, null) : await server.Ticket(true, ct);
|
||||
report.V4Refusal = ticket4.Refusal?.Reason;
|
||||
report.V6Refusal = ticket6.Refusal?.Reason;
|
||||
report.RetryAfterSeconds = Math.Max(ticket4.Refusal?.RetryAfterSeconds ?? 0, ticket6.Refusal?.RetryAfterSeconds ?? 0);
|
||||
@@ -92,8 +92,8 @@ public class Reach(ServerProbe server, Leftovers leftovers)
|
||||
: [.. Wellknown, answerer.Port];
|
||||
ports4 = ports4.Where(p => p is 80 or 443 or (>= 49152 and <= 65535)).Distinct().Take(3).ToList();
|
||||
|
||||
var knock4 = ticket4.Ticket is null ? Task.FromResult<(ViewProbe, ViewRefusal)>((null, null)) : server.Knock(run.Site ?? Endpoints.Main, false, ticket4.Ticket.Id, ports4, ct);
|
||||
var knock6 = ticket6.Ticket is null ? Task.FromResult<(ViewProbe, ViewRefusal)>((null, null)) : server.Knock(run.Site ?? Endpoints.Main, true, ticket6.Ticket.Id, [answerer.Port], ct);
|
||||
var knock4 = ticket4.Ticket is null ? Task.FromResult<(ViewProbe, ViewRefusal)>((null, null)) : server.Knock(false, ticket4.Ticket.Id, ports4, ct);
|
||||
var knock6 = ticket6.Ticket is null ? Task.FromResult<(ViewProbe, ViewRefusal)>((null, null)) : server.Knock(true, ticket6.Ticket.Id, [answerer.Port], ct);
|
||||
var (probe4, refusal4) = await knock4;
|
||||
var (probe6, refusal6) = await knock6;
|
||||
report.V4 = probe4;
|
||||
|
||||
@@ -56,15 +56,15 @@ public class ServerProbe(HttpClient https, HttpClient plain)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(ViewProbeTicket Ticket, ViewRefusal Refusal)> Ticket(MeasuringSite site, bool v6, CancellationToken ct)
|
||||
public async Task<(ViewProbeTicket Ticket, ViewRefusal Refusal)> Ticket(bool v6, CancellationToken ct)
|
||||
{
|
||||
var origin = v6 ? site.BaseV6 : site.BaseV4;
|
||||
var origin = v6 ? Endpoints.BaseV6 : Endpoints.BaseV4;
|
||||
return await Send(HttpMethod.Post, origin + Endpoints.Probe.POST_Ticket, null, MeWebJsonContext.Default.ViewProbeTicket, ct);
|
||||
}
|
||||
|
||||
public async Task<(ViewProbe Probe, ViewRefusal Refusal)> Knock(MeasuringSite site, bool v6, string id, List<int> ports, CancellationToken ct)
|
||||
public async Task<(ViewProbe Probe, ViewRefusal Refusal)> Knock(bool v6, string id, List<int> ports, CancellationToken ct)
|
||||
{
|
||||
var origin = v6 ? site.BaseV6 : site.BaseV4;
|
||||
var origin = v6 ? Endpoints.BaseV6 : Endpoints.BaseV4;
|
||||
var form = JsonContent.Create(new ProbeForm { Ports = ports }, MeWebJsonContext.Default.ProbeForm);
|
||||
return await Send(HttpMethod.Post, origin + Endpoints.Probe.POST_M_Run(id), form, MeWebJsonContext.Default.ViewProbe, ct, TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
@@ -313,9 +313,7 @@ public static class Verdicts
|
||||
if (reach.V6 is null && reach.V6Refusal is not null) finding.WithKey("FactReachV6", "Refusal" + Pascal(reach.V6Refusal));
|
||||
if (reach.Hairpin is not null) finding.WithKey("FactHairpin", reach.Hairpin == true ? "ValueHairpinYes" : "ValueHairpinNo");
|
||||
var route = reach.V4?.Route ?? reach.V6?.Route ?? [];
|
||||
var site = run?.Site ?? Endpoints.Main;
|
||||
if (route.Count > 0) finding.With(site == Endpoints.Main ? "FactRouteBack" : "FactRouteBackNode", string.Join(" → ", route.Select(h => h.Name ?? h.Address ?? "*")));
|
||||
if (Endpoints.Nodes.Count > 0) finding.With("FactMeasuredFrom", $"{site.Place} ({site.Host})");
|
||||
if (route.Count > 0) finding.With("FactRouteBack", string.Join(" → ", route.Select(h => h.Name ?? h.Address ?? "*")));
|
||||
if (reach.V4?.Ports.Any(p => p.Outcome == ProbeOutcome.OpenNotYou && p.Port is 80 or 443) == true && !mapped)
|
||||
finding.WithKey("FactHint", "HintSomethingAnswers");
|
||||
return finding;
|
||||
@@ -392,7 +390,7 @@ public static class Verdicts
|
||||
return finding;
|
||||
}
|
||||
|
||||
public static Finding Speed(SpeedReport speed, MeasuringSite site)
|
||||
public static Finding Speed(SpeedReport speed)
|
||||
{
|
||||
var finding = new Finding { Id = CheckId.Speed };
|
||||
if (speed.Limited)
|
||||
@@ -413,8 +411,7 @@ public static class Verdicts
|
||||
finding.WithKey("FactDownload", "ValueMbpsDecimal", speed.DownMbps.ToString("0.#", CultureInfo.InvariantCulture));
|
||||
finding.WithKey("FactUpload", "ValueMbpsDecimal", speed.UpMbps.ToString("0.#", CultureInfo.InvariantCulture));
|
||||
finding.WithKey("FactSample", "ValueSample", (speed.DownBytes / 1048576).ToString(CultureInfo.InvariantCulture), (speed.UpBytes / 1048576).ToString(CultureInfo.InvariantCulture));
|
||||
if (speed.RoundTripMs > 0) finding.WithKey(site == Endpoints.Main ? "FactRoundTrip" : "FactRoundTripNode", "ValueMilliseconds", speed.RoundTripMs.ToString(CultureInfo.InvariantCulture));
|
||||
if (Endpoints.Nodes.Count > 0) finding.With("FactMeasuredFrom", $"{site.Place} ({site.Host})");
|
||||
if (speed.RoundTripMs > 0) finding.WithKey("FactRoundTrip", "ValueMilliseconds", speed.RoundTripMs.ToString(CultureInfo.InvariantCulture));
|
||||
return finding;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class AsnTable
|
||||
{
|
||||
using var brotli = new BrotliStream(compressed, CompressionMode.Decompress);
|
||||
using var reader = new BinaryReader(brotli, Encoding.UTF8);
|
||||
if (reader.ReadUInt32() != Magic) throw new InvalidDataException("not a me-web? provider table");
|
||||
if (reader.ReadUInt32() != Magic) throw new InvalidDataException("not a “me-web?” provider table");
|
||||
var table = new AsnTable { SourceDate = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt64()) };
|
||||
table.systems = new AsnEntry[reader.Read7BitEncodedInt()];
|
||||
for (var i = 0; i < table.systems.Length; i++)
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class NetworkTable
|
||||
{
|
||||
using var brotli = new BrotliStream(compressed, CompressionMode.Decompress);
|
||||
using var reader = new BinaryReader(brotli, Encoding.UTF8);
|
||||
if (reader.ReadUInt32() != Magic) throw new InvalidDataException("not a me-web? network table");
|
||||
if (reader.ReadUInt32() != Magic) throw new InvalidDataException("not a “me-web?” network table");
|
||||
var table = new NetworkTable { BuiltAt = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt64()) };
|
||||
var count = reader.Read7BitEncodedInt();
|
||||
var previous = 0u;
|
||||
|
||||
@@ -18,17 +18,17 @@ public class SpeedReport
|
||||
|
||||
public static class SpeedCheck
|
||||
{
|
||||
public static async Task<SpeedReport> Run(HttpClient https, LinkKind kind, MeasuringSite site, CancellationToken ct)
|
||||
public static async Task<SpeedReport> Run(HttpClient https, LinkKind kind, CancellationToken ct)
|
||||
{
|
||||
var mobile = kind == LinkKind.Mobile;
|
||||
var report = new SpeedReport { DownBytes = mobile ? 2 * 1024 * 1024 : 12 * 1024 * 1024, UpBytes = mobile ? 1024 * 1024 : 4 * 1024 * 1024 };
|
||||
report.RoundTripMs = await RoundTrip(site.Host, ct);
|
||||
report.RoundTripMs = await RoundTrip(ct);
|
||||
try
|
||||
{
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(30));
|
||||
var clock = Stopwatch.StartNew();
|
||||
using (var response = await https.GetAsync(site.Base + Endpoints.Speed.GET_M_Down(report.DownBytes), HttpCompletionOption.ResponseHeadersRead, timeout.Token))
|
||||
using (var response = await https.GetAsync(Endpoints.Base + Endpoints.Speed.GET_M_Down(report.DownBytes), HttpCompletionOption.ResponseHeadersRead, timeout.Token))
|
||||
{
|
||||
if ((int)response.StatusCode == 429)
|
||||
{
|
||||
@@ -59,7 +59,7 @@ public static class SpeedCheck
|
||||
var payload = new byte[report.UpBytes];
|
||||
Random.Shared.NextBytes(payload);
|
||||
clock.Restart();
|
||||
using (var response = await https.PostAsync(site.Base + Endpoints.Speed.POST_Up, new ByteArrayContent(payload), timeout.Token))
|
||||
using (var response = await https.PostAsync(Endpoints.Base + Endpoints.Speed.POST_Up, new ByteArrayContent(payload), timeout.Token))
|
||||
{
|
||||
if (response.IsSuccessStatusCode) report.UpMbps = Mbps(payload.Length, clock.Elapsed);
|
||||
}
|
||||
@@ -70,20 +70,11 @@ public static class SpeedCheck
|
||||
return report;
|
||||
}
|
||||
|
||||
public static async Task<MeasuringSite> Nearest(CancellationToken ct)
|
||||
{
|
||||
if (Endpoints.Nodes.Count == 0) return Endpoints.Main;
|
||||
var sites = Endpoints.Nodes.Prepend(Endpoints.Main).ToList();
|
||||
var times = await Task.WhenAll(sites.Select(site => RoundTrip(site.Host, ct)));
|
||||
var best = Enumerable.Range(1, sites.Count - 1).Where(i => times[i] > 0).OrderBy(i => times[i]).FirstOrDefault();
|
||||
return best > 0 && (times[0] == 0 || times[best] * 10 < times[0] * 7) ? sites[best] : Endpoints.Main;
|
||||
}
|
||||
|
||||
static async Task<int> RoundTrip(string host, CancellationToken ct)
|
||||
static async Task<int> RoundTrip(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var addresses = await Dns.GetHostAddressesAsync(host, ct);
|
||||
var addresses = await Dns.GetHostAddressesAsync(Endpoints.Host, ct);
|
||||
if (addresses.Length == 0) return 0;
|
||||
var samples = new List<double>();
|
||||
for (var i = 0; i < 3; i++)
|
||||
|
||||
@@ -100,7 +100,7 @@ public static class PortMapper
|
||||
var (answer, status) = await Gateway.Soap(control, router.UpnpService, "AddPortMapping",
|
||||
$"<NewRemoteHost></NewRemoteHost><NewExternalPort>{external}</NewExternalPort><NewProtocol>TCP</NewProtocol>" +
|
||||
$"<NewInternalPort>{@internal}</NewInternalPort><NewInternalClient>{localAddress}</NewInternalClient><NewEnabled>1</NewEnabled>" +
|
||||
$"<NewPortMappingDescription>me-web? test</NewPortMappingDescription><NewLeaseDuration>{lease}</NewLeaseDuration>", ct);
|
||||
$"<NewPortMappingDescription>'me-web?' test</NewPortMappingDescription><NewLeaseDuration>{lease}</NewLeaseDuration>", ct);
|
||||
var error = status == 200 ? 0 : Gateway.ErrorCode(answer, status);
|
||||
if (error == 0)
|
||||
return (new Mapping
|
||||
|
||||
Reference in new issue
Block a user