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 { Settings } from "./index.svelte";
import { PUB_VERTD_URL } from "$env/static/public"; import { PUB_VERTD_URL } from "$env/static/public";
import { log } from "$lib/util/logger";
const LOCATIONS = [ const LOCATIONS = [
{ { url: "https://eu.vertd.vert.sh" },
latitude: 49.0976, { url: "https://usa.vertd.vert.sh" },
longitude: 12.4869,
url: "https://eu.vertd.vert.sh",
},
{
latitude: 47.6587,
longitude: -117.426,
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 = export type VertdInner =
| { type: "auto" } | { type: "auto" }
| { type: "eu" } | { type: "eu" }
@ -40,8 +16,6 @@ export type VertdInner =
export class VertdInstance { export class VertdInstance {
public static instance = new VertdInstance(); public static instance = new VertdInstance();
private cachedIp = $state<IpInfo | null>(null);
private inner = $state<VertdInner>({ private inner = $state<VertdInner>({
type: "auto", type: "auto",
}); });
@ -81,32 +55,45 @@ export class VertdInstance {
} }
public async url() { public async url() {
const reachable = async (url: string) => { const latency = async (url: string) => {
try { try {
const res = await fetch(url + "/api/version", { const start = performance.now();
await fetch(url, {
method: "GET", method: "GET",
cache: "no-store", cache: "no-store",
mode: "no-cors",
}); });
return res.ok; return performance.now() - start;
} catch { } catch {
return false; return Number.POSITIVE_INFINITY;
} }
}; };
switch (this.inner.type) { switch (this.inner.type) {
case "auto": { case "auto": {
if (!this.cachedIp) this.cachedIp = await ip(); const results = await Promise.all(
const ipInfo = this.cachedIp; LOCATIONS.map(async ({ url }) => ({
const primary = this.geographicallyOptimalInstance(ipInfo); url,
latency: await latency(url),
})),
);
// try primary (closest) first const fastest = results
if (await reachable(primary)) return primary; .filter((result) => Number.isFinite(result.latency))
.sort((a, b) => a.latency - b.latency)[0];
// fall back to other locations const latencySummary = results
for (const location of LOCATIONS) { .map(
if (location.url === primary) continue; (result) =>
if (await reachable(location.url)) return location.url; `${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 // if none are reachable, fall back to custom
return Settings.instance.settings.vertdURL; 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;
}
} }