MeWeb.Core runs seven checks concurrently and reports each as it settles:
online or behind a login page, this connection, the public IPv4 and IPv6
seen from v4./v6.meweb, the provider from a built-in map, the router
(UPnP IGD, NAT-PMP and PCP, read-only), address sharing (router WAN
against the echo, TTL hops only as a hint), and the history kept on the
device. The app draws the path from this device to the internet and lights
it node by node, with a card per check and a sheet listing every host it
contacts.
The provider map is iptoasn's table compressed to 2.1 MB by
tools/knowledge.cs and built before every app release; eleven Italian
provider profiles carry each fact with who said it, the source and the
month. The server gains a download page (en, it) and /download/{platform}
from the latest Gitea release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
47 lines
1.9 KiB
C#
47 lines
1.9 KiB
C#
using System.Net.Sockets;
|
|
|
|
using MeWeb.Shared;
|
|
|
|
namespace MeWeb.Server;
|
|
|
|
public static class ServerApi
|
|
{
|
|
public static WebApplication MapMeWebApi(this WebApplication app)
|
|
{
|
|
app.MapGet(Endpoints.Server.GET_Info, Info);
|
|
app.MapGet(Endpoints.Server.GET_Ip, Ip);
|
|
app.MapGet(Endpoints.Downloads.GET_Page, Page);
|
|
app.MapGet(Endpoints.Downloads.GET_Platform, Download);
|
|
return app;
|
|
}
|
|
|
|
static ViewInfo Info() => new() { Version = BuildInfo.Version, Commit = BuildInfo.Commit, BuiltAt = BuildInfo.BuiltAt };
|
|
|
|
static IResult Ip(HttpContext context)
|
|
{
|
|
var address = context.Connection.RemoteIpAddress;
|
|
if (address is null) return Results.BadRequest();
|
|
if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4();
|
|
var family = address.AddressFamily == AddressFamily.InterNetworkV6 ? "ipv6" : "ipv4";
|
|
return Results.Ok(new ViewIp { Address = address.ToString(), Family = family });
|
|
}
|
|
|
|
static async Task<IResult> Page(HttpContext context, ReleaseFeed feed, CancellationToken ct)
|
|
{
|
|
var asked = context.Request.Query["lang"].ToString();
|
|
var language = asked is "it" or "en" ? asked : context.Request.Headers.AcceptLanguage.ToString().StartsWith("it", StringComparison.OrdinalIgnoreCase) ? "it" : "en";
|
|
var release = await feed.Latest(ct);
|
|
context.Response.Headers.CacheControl = "no-cache";
|
|
context.Response.Headers.ContentLanguage = language;
|
|
context.Response.Headers.Vary = "Accept-Language";
|
|
return Results.Content(DownloadPage.Render(release, language), "text/html; charset=utf-8");
|
|
}
|
|
|
|
static async Task<IResult> Download(string platform, ReleaseFeed feed, CancellationToken ct)
|
|
{
|
|
if (!ReleaseFeed.Platforms.Any(p => p.Platform == platform)) return Results.NotFound();
|
|
var asset = ReleaseFeed.Asset(await feed.Latest(ct), platform);
|
|
return asset?.Url is null ? Results.Redirect(Endpoints.Gitea.Releases) : Results.Redirect(asset.Url);
|
|
}
|
|
}
|