feat: use close relays

This commit is contained in:
Kieran 2023-11-17 20:03:59 +00:00
parent 55c938961f
commit 588c3756fd
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
8 changed files with 146 additions and 9 deletions

View File

@ -1,5 +1,4 @@
.relay {
background-color: var(--gray-secondary);
border-radius: 5px;
display: grid;
grid-template-columns: min-content auto;

View File

@ -41,7 +41,7 @@ export default function Relay(props: RelayProps) {
return (
<>
<div className="relay">
<div className="relay bg-dark">
<div className={classNames("flex items-center", state?.connected ? "bg-success" : "bg-error")}>
<RelayFavicon url={props.addr} />
</div>

View File

@ -68,6 +68,16 @@ export interface TranslationResponse {
}>;
}
export interface RelayDistance {
url: string;
distance: number;
users: number;
country?: string;
city?: string;
is_paid?: boolean;
description?: string;
}
export default class SnortApi {
#url: string;
#publisher?: EventPublisher;
@ -121,6 +131,10 @@ export default class SnortApi {
return this.#getJson<TranslationResponse | object>("api/v1/translate", "POST", tx);
}
closeRelays(lat: number, lon: number, count = 5) {
return this.#getJson<Array<RelayDistance>>(`api/v1/relays?count=${count}`, "POST", { lat, lon });
}
async #getJsonAuthd<T>(
path: string,
method?: "GET" | string,

View File

@ -15,10 +15,11 @@ import * as utils from "@noble/curves/abstract/utils";
import { DefaultRelays, SnortPubKey } from "Const";
import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "Login";
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
import { bech32ToHex, dedupeById, sanitizeRelayUrl, unwrap } from "SnortUtils";
import { bech32ToHex, dedupeById, getCountry, sanitizeRelayUrl, unwrap } from "SnortUtils";
import { SubscriptionEvent } from "Subscription";
import { Chats, FollowsFeed, GiftsCache, Notifications } from "Cache";
import { Nip7OsSigner } from "./Nip7OsSigner";
import SnortApi from "External/SnortApi";
export function setRelays(state: LoginSession, relays: Record<string, RelaySettings>, createdAt: number) {
if (SINGLE_RELAY) {
@ -94,6 +95,21 @@ export async function generateNewLogin(
const privateKey = entropyToPrivateKey(ent);
const newRelays = Object.fromEntries(DefaultRelays.entries());
// Use current timezone info to determine approx location
// use closest 5 relays
const country = getCountry();
const api = new SnortApi();
const closeRelays = await api.closeRelays(country.lat, country.lon, 10);
for (const cr of closeRelays.sort((a, b) => (a.distance > b.distance ? 1 : -1)).filter(a => !a.is_paid)) {
const rr = sanitizeRelayUrl(cr.url);
if (rr) {
newRelays[rr] = { read: true, write: true };
if (Object.keys(newRelays).length >= 5) {
break;
}
}
}
// connect to new relays
await Promise.all(Object.entries(newRelays).map(([k, v]) => system.ConnectToRelay(k, v)));

View File

@ -1,6 +1,6 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { FormattedMessage } from "react-intl";
import { unixNowMs } from "@snort/shared";
import { unixNowMs, unwrap } from "@snort/shared";
import { EventPublisher, FullRelaySettings, RelaySettings, SystemInterface } from "@snort/system";
import Relay from "Element/Relay/Relay";
@ -8,9 +8,11 @@ import useEventPublisher from "Hooks/useEventPublisher";
import useLogin from "Hooks/useLogin";
import { setRelays } from "Login";
import AsyncButton from "Element/AsyncButton";
import SnortApi, { RelayDistance } from "External/SnortApi";
import { getCountry, getRelayName, sanitizeRelayUrl } from "SnortUtils";
import { formatShort } from "Number";
import messages from "./messages";
const Blasters = ["wss://nostr.mutinywallet.com"];
export async function saveRelays(
@ -88,6 +90,7 @@ const RelaySettingsPage = () => {
<FormattedMessage {...messages.Save} />
</AsyncButton>
{addRelay()}
<CloseRelays />
<h3>
<FormattedMessage defaultMessage="Other Connections" />
</h3>
@ -101,3 +104,89 @@ const RelaySettingsPage = () => {
};
export default RelaySettingsPage;
export function CloseRelays() {
const [relays, setRecommendedRelays] = useState<Array<RelayDistance>>();
const country = getCountry();
const [location, setLocation] = useState<{ lat: number; lon: number }>(country);
const login = useLogin();
const relayUrls = Object.keys(login.relays.item);
async function loadRelays() {
const api = new SnortApi();
setRecommendedRelays(await api.closeRelays(location.lat, location.lon, 10));
}
useEffect(() => {
loadRelays().catch(console.error);
}, [location]);
return (
<>
<h3>
<FormattedMessage defaultMessage="Recommended Relays" />
</h3>
{"geolocation" in navigator && (
<AsyncButton
onClick={async () => {
try {
const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
setLocation({
lat: pos.coords.latitude,
lon: pos.coords.longitude,
});
} catch (e) {
console.error(e);
}
}}>
<FormattedMessage defaultMessage="Use Exact Location" />
</AsyncButton>
)}
{relays
?.filter(a => !relayUrls.includes(unwrap(sanitizeRelayUrl(a.url))) && !a.is_paid)
.sort((a, b) => (a.distance > b.distance ? 1 : -1))
.map(a => (
<div className="bg-dark p br flex flex-col g8">
<div className="flex justify-between items-center">
<div className="bold">{getRelayName(a.url)}</div>
<AsyncButton
onClick={async () => {
setRelays(
login,
{
...login.relays.item,
[a.url]: { read: true, write: true },
},
unixNowMs(),
);
}}>
<FormattedMessage defaultMessage="Add" />
</AsyncButton>
</div>
<div className="flex flex-col g8">
<span>{a.description}</span>
<small>
<FormattedMessage
defaultMessage="{n} km - {location}"
values={{
n: (a.distance / 1000).toFixed(0),
location: a.city ? `${a.city}, ${a.country}` : a.country,
}}
/>
</small>
<small>
<FormattedMessage
defaultMessage="{n} users"
values={{
n: formatShort(a.users),
}}
/>
</small>
</div>
</div>
))}
</>
);
}

View File

@ -517,12 +517,15 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk
export function getCountry() {
const tz = Intl.DateTimeFormat().resolvedOptions();
const info = (TZ as Record<string, Array<string> | undefined>)[tz.timeZone];
const [, lat, lon] = info?.[1].split(/[-+]/) ?? ["", "00", "000"];
const pos = info?.[1];
const sep = Number(pos?.slice(1).search(/[-+]/)) + 1;
const [lat, lon] = [pos?.slice(0, sep) ?? "00", pos?.slice(sep) ?? "000"];
return {
zone: tz.timeZone,
country: info?.[0],
lat: Number(lat) / Math.pow(10, lat.length - 2),
lon: Number(lon) / Math.pow(10, lon.length - 3),
lat: Number(lat) / Math.pow(10, lat.length - 3),
lon: Number(lon) / Math.pow(10, lon.length - 4),
info,
};
}

View File

@ -66,6 +66,9 @@
"0BUTMv": {
"defaultMessage": "Search..."
},
"0HFX0T": {
"defaultMessage": "Use Exact Location"
},
"0jOEtS": {
"defaultMessage": "Invalid LNURL"
},
@ -81,6 +84,9 @@
"0yO7wF": {
"defaultMessage": "{n} secs"
},
"1H4Keq": {
"defaultMessage": "{n} users"
},
"1Mo59U": {
"defaultMessage": "Are you sure you want to remove this note from bookmarks?"
},
@ -834,6 +840,9 @@
"UrKTqQ": {
"defaultMessage": "You have an active iris.to account"
},
"VL900k": {
"defaultMessage": "Recommended Relays"
},
"VN0+Fz": {
"defaultMessage": "Balance: {amount} sats"
},
@ -1158,6 +1167,9 @@
"jMzO1S": {
"defaultMessage": "Internal error: {msg}"
},
"jTrbGf": {
"defaultMessage": "{n} km - {location}"
},
"jfV8Wr": {
"defaultMessage": "Back",
"description": "Navigate back button on threads view"

View File

@ -21,11 +21,13 @@
"08zn6O": "Export Keys",
"0Azlrb": "Manage",
"0BUTMv": "Search...",
"0HFX0T": "Use Exact Location",
"0jOEtS": "Invalid LNURL",
"0mch2Y": "name has disallowed characters",
"0siT4z": "Politics",
"0uoY11": "Show Status",
"0yO7wF": "{n} secs",
"1H4Keq": "{n} users",
"1Mo59U": "Are you sure you want to remove this note from bookmarks?",
"1R43+L": "Enter Nostr Wallet Connect config",
"1c4YST": "Connected to: {node} 🎉",
@ -274,6 +276,7 @@
"Ub+AGc": "Sign In",
"Up5U7K": "Block",
"UrKTqQ": "You have an active iris.to account",
"VL900k": "Recommended Relays",
"VN0+Fz": "Balance: {amount} sats",
"VOjC1i": "Pick which upload service you want to upload attachments to",
"VR5eHw": "Public key (npub/nprofile)",
@ -381,6 +384,7 @@
"jAmfGl": "Your {site_name} subscription is expired",
"jHa/ko": "Clean up your feed",
"jMzO1S": "Internal error: {msg}",
"jTrbGf": "{n} km - {location}",
"jfV8Wr": "Back",
"jvo0vs": "Save",
"jzgQ2z": "{n} Reactions",