feat: close relays
This commit is contained in:
parent
623e5f68a0
commit
4c80fb78ec
@ -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;
|
||||
|
@ -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);
|
67
packages/app/src/Hooks/useCloseRelays.ts
Normal file
67
packages/app/src/Hooks/useCloseRelays.ts
Normal 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()];
|
||||
}
|
@ -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>();
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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",
|
||||
];
|
||||
|
@ -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>,
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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!",
|
||||
|
Loading…
x
Reference in New Issue
Block a user