feat: close relays
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
kieran 2024-09-11 21:41:37 +01:00
parent 623e5f68a0
commit 4c80fb78ec
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
10 changed files with 182 additions and 16 deletions

View File

@ -2,12 +2,12 @@ import { Link } from "react-router-dom";
import useRelayState from "@/Feed/RelayState";
import useLogin from "@/Hooks/useLogin";
import RelayUptime from "@/Pages/settings/relays/uptime";
import { getRelayName } from "@/Utils";
import Icon from "../Icons/Icon";
import RelayPermissions from "./permissions";
import RelayStatusLabel from "./status-label";
import RelayUptime from "./uptime";
export interface RelayProps {
addr: string;

View File

@ -1,20 +1,13 @@
import { sanitizeRelayUrl, unixNow } from "@snort/shared";
import { EventKind, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import classNames from "classnames";
import { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { findTag } from "@/Utils";
import { Day } from "@/Utils/Const";
import { Day, MonitorRelays } from "@/Utils/Const";
import UptimeLabel from "./uptime-label";
const MonitorRelays = [
"wss://relaypag.es",
"wss://relay.nostr.watch",
"wss://history.nostr.watch",
"wss://monitorlizard.nostr1.com",
];
export default function RelayUptime({ url }: { url: string }) {
const sub = useMemo(() => {
const u = sanitizeRelayUrl(url);

View File

@ -0,0 +1,67 @@
import { unixNow } from "@snort/shared";
import { EventKind, NostrEvent, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import Geohash from "latlon-geohash";
import { useMemo } from "react";
import { calculateDistance, findTag, getCountry } from "@/Utils";
import { Day, MonitorRelays } from "@/Utils/Const";
interface RelayDistance {
distance: number;
event: NostrEvent;
addr: string;
}
export function useCloseRelays() {
const country = getCountry();
const sub = useMemo(() => {
const geoHash = Geohash.encode(country.lat, country.lon);
const geoHashes = [];
for (let x = 2; x < geoHash.length; x++) {
geoHashes.push(geoHash.substring(0, x));
}
const rb = new RequestBuilder(`close-relays:${country}`);
rb.withFilter()
.kinds([30_166 as EventKind])
.tag("g", geoHashes)
.since(unixNow() - Day)
.relay(MonitorRelays);
return rb;
}, [country]);
const data = useRequestBuilder(sub);
const distRelays = data
.map(a => {
const lowestDistance = a.tags
.filter(a => a[0] === "g" && a[1].length > 5)
.map(a => {
const g = Geohash.decode(a[1]);
return calculateDistance(g.lat, g.lon, country.lat, country.lon);
})
.reduce((acc, v) => (v < acc ? v : acc), Number.MAX_VALUE);
return {
distance: lowestDistance,
event: a,
addr: findTag(a, "d") ?? "",
} as RelayDistance;
})
.sort((a, b) => (b.distance < a.distance ? 1 : -1))
.reduce((acc, v) => {
const u = new URL(v.addr);
if (!acc.has(u.hostname)) {
acc.set(u.hostname, {
...v,
addr: `${u.protocol}//${u.host}/`,
});
}
if (acc.get(u.hostname)!.distance > v.distance) {
acc.set(u.hostname, v);
}
return acc;
}, new Map<string, RelayDistance>());
return [...distRelays.values()];
}

View File

@ -11,12 +11,11 @@ import RelayPermissions from "@/Components/Relay/permissions";
import { RelayFavicon } from "@/Components/Relay/RelaysMetadata";
import RelaySoftware from "@/Components/Relay/software";
import RelayStatusLabel from "@/Components/Relay/status-label";
import RelayUptime from "@/Components/Relay/uptime";
import ProfileImage from "@/Components/User/ProfileImage";
import useRelayState from "@/Feed/RelayState";
import { getRelayName, parseId } from "@/Utils";
import RelayUptime from "./relays/uptime";
const RelayInfo = () => {
const params = useParams();
const [info, setInfo] = useState<RI>();

View File

@ -1,19 +1,21 @@
/* eslint-disable max-lines */
import { dedupe, removeUndefined, sanitizeRelayUrl } from "@snort/shared";
import { OutboxModel } from "@snort/system";
import { SnortContext } from "@snort/system-react";
import { useContext, useMemo, useSyncExternalStore } from "react";
import { FormattedMessage } from "react-intl";
import { FormattedMessage, FormattedNumber } from "react-intl";
import { Link } from "react-router-dom";
import { RelayMetrics } from "@/Cache";
import AsyncButton from "@/Components/Button/AsyncButton";
import { CollapsedSection } from "@/Components/Collapsed";
import { RelayFavicon } from "@/Components/Relay/RelaysMetadata";
import RelayUptime from "@/Components/Relay/uptime";
import UptimeLabel from "@/Components/Relay/uptime-label";
import useLogin from "@/Hooks/useLogin";
import { getRelayName } from "@/Utils";
import RelayUptime from "./uptime";
import UptimeLabel from "./uptime-label";
import { useCloseRelays } from "@/Hooks/useCloseRelays";
import Uptime from "@/Components/Relay/uptime";
export function DiscoverRelays() {
const { follows, relays, state } = useLogin(l => ({
@ -56,6 +58,7 @@ export function DiscoverRelays() {
[relays, metrics],
);
const closeRelays = useCloseRelays();
return (
<div className="flex flex-col gap-4">
<CollapsedSection
@ -161,6 +164,68 @@ export function DiscoverRelays() {
</tbody>
</table>
</CollapsedSection>
<CollapsedSection
title={
<div className="text-xl">
<FormattedMessage defaultMessage="Close Relays" />
</div>
}>
<small>
<FormattedMessage defaultMessage="Relays close to your geographic location." />
</small>
<table className="table">
<thead>
<tr className="text-gray-light uppercase">
<th>
<FormattedMessage defaultMessage="Relay" description="Relay name (URL)" />
</th>
<th>
<FormattedMessage defaultMessage="Distance" />
</th>
<th>
<FormattedMessage defaultMessage="Uptime" />
</th>
<th></th>
</tr>
</thead>
<tbody>
{closeRelays
.filter(a => !isNaN(a.distance))
.slice(0, 100)
.map(a => (
<tr key={a.addr}>
<td title={a.addr}>
<Link to={`/settings/relays/${encodeURIComponent(a.addr)}`} className="flex gap-2 items-center">
<RelayFavicon url={a.addr} />
{getRelayName(a.addr)}
</Link>
</td>
<td className="text-center">
<FormattedMessage
defaultMessage="{n} km"
values={{
n: <FormattedNumber value={a.distance / 1000} maximumFractionDigits={0} />,
}}
/>
</td>
<td className="text-center">
<Uptime url={a.addr} />
</td>
<td className="text-end">
<AsyncButton
className="!py-1 mb-1"
onClick={async () => {
await state.addRelay(a.addr, { read: true, write: true });
}}>
<FormattedMessage defaultMessage="Add" />
</AsyncButton>
</td>
</tr>
))}
</tbody>
</table>
</CollapsedSection>
</div>
);
}

View File

@ -160,3 +160,13 @@ export const MaxAboutLength = 1000;
* Snort backend publishes rates
*/
export const SnortPubkey = "npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws";
/**
* List of relay monitor relays
*/
export const MonitorRelays = [
"wss://relaypag.es",
"wss://relay.nostr.watch",
"wss://history.nostr.watch",
"wss://monitorlizard.nostr1.com",
];

View File

@ -530,6 +530,22 @@ export function getCountry() {
};
}
export function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371000; // Radius of the Earth in meters
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function toRadians(degrees: number): number {
return degrees * (Math.PI / 180);
}
export function trackEvent(
event: string,
props?: Record<string, string | boolean>,

View File

@ -333,6 +333,9 @@
"7hp70g": {
"defaultMessage": "NIP-05"
},
"7pFGAQ": {
"defaultMessage": "Close Relays"
},
"8/vBbP": {
"defaultMessage": "Reposts ({n})"
},
@ -952,6 +955,9 @@
"P7nJT9": {
"defaultMessage": "Total today (UTC): {amount} sats"
},
"P8JC58": {
"defaultMessage": "Distance"
},
"PCSt5T": {
"defaultMessage": "Preferences"
},
@ -1051,6 +1057,9 @@
"StKzTE": {
"defaultMessage": "The author has marked this note as a <i>sensitive topic</i>"
},
"T83nqf": {
"defaultMessage": "Relays close to your geographic location."
},
"TDR5ge": {
"defaultMessage": "Media in notes will automatically be shown for selected people, otherwise only the link will show"
},
@ -1256,6 +1265,9 @@
"aWpBzj": {
"defaultMessage": "Show more"
},
"abbGKq": {
"defaultMessage": "{n} km"
},
"b12Goz": {
"defaultMessage": "Mnemonic"
},

View File

@ -110,6 +110,7 @@
"7UOvbT": "Offline",
"7YkSA2": "Community Leader",
"7hp70g": "NIP-05",
"7pFGAQ": "Close Relays",
"8/vBbP": "Reposts ({n})",
"89q5wc": "Confirm Reposts",
"8BDFvJ": "Conventions for clients' use of e and p tags in text events",
@ -315,6 +316,7 @@
"P61BTu": "Copy Event JSON",
"P7FD0F": "System (Default)",
"P7nJT9": "Total today (UTC): {amount} sats",
"P8JC58": "Distance",
"PCSt5T": "Preferences",
"PXQ0z0": "Receiving to <b>{wallet}</b>",
"PamNxw": "Unknown file header: {name}",
@ -348,6 +350,7 @@
"SmuYUd": "What should we call you?",
"Ss0sWu": "Pay Now",
"StKzTE": "The author has marked this note as a <i>sensitive topic</i>",
"T83nqf": "Relays close to your geographic location.",
"TDR5ge": "Media in notes will automatically be shown for selected people, otherwise only the link will show",
"TH1fFo": "Telegram",
"TJo5E6": "Preview",
@ -416,6 +419,7 @@
"aRex7h": "Paid {amount} sats, fee {fee} sats",
"aSGz4J": "Connect to your own LND node with Lightning Node Connect",
"aWpBzj": "Show more",
"abbGKq": "{n} km",
"b12Goz": "Mnemonic",
"b5vAk0": "Your handle will act like a lightning address and will redirect to your chosen LNURL or Lightning address",
"bF1MYT": "You are a community leader and are earning <b>{percent}</b> of referred users subscriptions!",