feat: ping servers instead of ip location (#240)

much better for privacy :p - fixes #240
This commit is contained in:
Maya 2026-03-20 10:26:11 +03:00
parent 7ebc535fa4
commit f038dfc453
1 changed files with 30 additions and 69 deletions

View File

@ -1,36 +1,12 @@
import { ip, type IpInfo } from "$lib/util/ip";
import { Settings } from "./index.svelte";
import { PUB_VERTD_URL } from "$env/static/public";
import { log } from "$lib/util/logger";
const LOCATIONS = [
{
latitude: 49.0976,
longitude: 12.4869,
url: "https://eu.vertd.vert.sh",
},
{
latitude: 47.6587,
longitude: -117.426,
url: "https://usa.vertd.vert.sh",
},
{ url: "https://eu.vertd.vert.sh" },
{ url: "https://usa.vertd.vert.sh" },
];
const toRad = (value: number) => (value * Math.PI) / 180;
const haversine = (lat1: number, lon1: number, lat2: number, lon2: number) => {
const R = 6371; // km
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) *
Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c;
return d;
};
export type VertdInner =
| { type: "auto" }
| { type: "eu" }
@ -40,8 +16,6 @@ export type VertdInner =
export class VertdInstance {
public static instance = new VertdInstance();
private cachedIp = $state<IpInfo | null>(null);
private inner = $state<VertdInner>({
type: "auto",
});
@ -81,32 +55,45 @@ export class VertdInstance {
}
public async url() {
const reachable = async (url: string) => {
const latency = async (url: string) => {
try {
const res = await fetch(url + "/api/version", {
const start = performance.now();
await fetch(url, {
method: "GET",
cache: "no-store",
mode: "no-cors",
});
return res.ok;
return performance.now() - start;
} catch {
return false;
return Number.POSITIVE_INFINITY;
}
};
switch (this.inner.type) {
case "auto": {
if (!this.cachedIp) this.cachedIp = await ip();
const ipInfo = this.cachedIp;
const primary = this.geographicallyOptimalInstance(ipInfo);
const results = await Promise.all(
LOCATIONS.map(async ({ url }) => ({
url,
latency: await latency(url),
})),
);
// try primary (closest) first
if (await reachable(primary)) return primary;
const fastest = results
.filter((result) => Number.isFinite(result.latency))
.sort((a, b) => a.latency - b.latency)[0];
// fall back to other locations
for (const location of LOCATIONS) {
if (location.url === primary) continue;
if (await reachable(location.url)) return location.url;
}
const latencySummary = results
.map(
(result) =>
`${result.url} = ${Number.isFinite(result.latency) ? `${result.latency.toFixed(2)}ms` : "unreachable"}`,
)
.join("\n");
log(
["settings", "vertd"],
`vertd latency results: ${latencySummary}`,
);
if (fastest) return fastest.url;
// if none are reachable, fall back to custom
return Settings.instance.settings.vertdURL;
@ -125,30 +112,4 @@ export class VertdInstance {
}
}
}
private geographicallyOptimalInstance(ip: IpInfo) {
let bestLocation = LOCATIONS[0];
let bestDistance = haversine(
ip.latitude,
ip.longitude,
bestLocation.latitude,
bestLocation.longitude,
);
for (let i = 1; i < LOCATIONS.length; i++) {
const location = LOCATIONS[i];
const distance = haversine(
ip.latitude,
ip.longitude,
location.latitude,
location.longitude,
);
if (distance < bestDistance) {
bestDistance = distance;
bestLocation = location;
}
}
return bestLocation.url;
}
}