feat: relay controls
Some checks reported errors
continuous-integration/drone/push Build encountered an error

This commit is contained in:
kieran 2024-05-16 16:00:26 +01:00
parent 85151ac008
commit 5c8cb7d359
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
5 changed files with 145 additions and 8 deletions

View File

@ -12,8 +12,9 @@ import { NewPage } from "./page/new";
import { TorrentPage } from "./page/torrent"; import { TorrentPage } from "./page/torrent";
import { SearchPage } from "./page/search"; import { SearchPage } from "./page/search";
import { System, initSystem } from "./system"; import { System, initSystem } from "./system";
import { RelaysPage } from "./page/relays";
const Routes = [ const routes = [
{ {
element: <Layout />, element: <Layout />,
loader: async () => { loader: async () => {
@ -41,15 +42,19 @@ const Routes = [
path: "/search/:term?", path: "/search/:term?",
element: <SearchPage />, element: <SearchPage />,
}, },
{
path: "/relays",
element: <RelaysPage />,
},
], ],
}, },
] as Array<RouteObject>; ] as Array<RouteObject>;
const Router = createBrowserRouter(Routes); const router = createBrowserRouter(routes);
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<SnortContext.Provider value={System}> <SnortContext.Provider value={System}>
<RouterProvider router={Router} /> <RouterProvider router={router} />
</SnortContext.Provider> </SnortContext.Provider>
</React.StrictMode>, </React.StrictMode>,
); );

View File

@ -3,9 +3,35 @@ import { Button } from "../element/button";
import { LoginSession, LoginState, useLogin } from "../login"; import { LoginSession, LoginState, useLogin } from "../login";
import { ProfileImage } from "../element/profile-image"; import { ProfileImage } from "../element/profile-image";
import { Search } from "../element/search"; import { Search } from "../element/search";
import { useRelays } from "../relays";
import { useContext, useEffect } from "react";
import { SnortContext } from "@snort/system-react";
import { RelaySettings, SystemInterface } from "@snort/system";
export function Layout() { export function Layout() {
const login = useLogin(); const login = useLogin();
const system = useContext(SnortContext);
const { relays } = useRelays();
async function updateRelayConnections(system: SystemInterface, relays: Record<string, RelaySettings>) {
if (import.meta.env.VITE_SINGLE_RELAY) {
system.ConnectToRelay(import.meta.env.VITE_SINGLE_RELAY, { read: true, write: true });
} else {
for (const [k, v] of Object.entries(relays)) {
// note: don't awit this, causes race condition with sending requests to relays
system.ConnectToRelay(k, v);
}
for (const [k, v] of system.pool) {
if (!relays[k] && !v.ephemeral) {
system.DisconnectRelay(k);
}
}
}
}
useEffect(() => {
updateRelayConnections(system, Object.fromEntries(relays.map((a) => [a, { read: true, write: true }])));
}, [system, relays]);
async function DoLogin() { async function DoLogin() {
if ("nostr" in window) { if ("nostr" in window) {
@ -18,14 +44,18 @@ export function Layout() {
return ( return (
<div className="container mx-auto"> <div className="container mx-auto">
<header className="flex justify-between items-center pt-4 pb-6"> <header className="flex gap-4 items-center pt-4 pb-6">
<Link to={"/"} className="flex gap-2 items-center"> <Link to={"/"} className="flex gap-2 items-center">
<img src="/logo_256.jpg" className="rounded-full" height={40} width={40} /> <img src="/logo_256.jpg" className="rounded-full" height={40} width={40} />
<h1 className="font-bold uppercase">dtan.xyz</h1> <h1 className="font-bold uppercase">dtan.xyz</h1>
</Link> </Link>
<div className="w-1/2"> <div className="w-1/3">
<Search /> <Search />
</div> </div>
<div className="grow"></div>
<Link to="/relays">
<Button type="secondary">Relays</Button>
</Link>
{login ? ( {login ? (
<LoggedInHeader login={login} /> <LoggedInHeader login={login} />
) : ( ) : (

47
src/page/relays.tsx Normal file
View File

@ -0,0 +1,47 @@
import { useState } from "react";
import { Button } from "../element/button";
import { useRelays } from "../relays";
import { sanitizeRelayUrl } from "@snort/shared";
export function RelaysPage() {
const relays = useRelays();
const [newRelay, setNewRelay] = useState("");
return (
<>
<h2>Relays</h2>
<br />
<div className="flex flex-col gap-2">
{relays.relays.map((a) => (
<div key={a} className="bg-neutral-800 px-3 py-2 rounded-xl flex justify-between items-center">
{a}
<Button type="danger" onClick={() => relays.remove(a)}>
Remove
</Button>
</div>
))}
</div>
<br />
<div className="flex gap-4">
<input
type="text"
value={newRelay}
onChange={(e) => setNewRelay(e.target.value)}
className="px-4 py-2 rounded-xl bg-neutral-800 focus-visible:outline-none"
placeholder="wss://myrelay.com"
/>
<Button
type="primary"
onClick={() => {
const url = sanitizeRelayUrl(newRelay);
if (url) {
relays.add(url);
setNewRelay("");
}
}}
>
Add
</Button>
</div>
</>
);
}

58
src/relays.tsx Normal file
View File

@ -0,0 +1,58 @@
import { ExternalStore, appendDedupe, sanitizeRelayUrl } from "@snort/shared";
import { useSyncExternalStore } from "react";
const storageKey = "relays";
class RelaysStore extends ExternalStore<Array<string>> {
#relays: Array<string> = [];
constructor() {
super();
const loaded = localStorage.getItem(storageKey);
if (loaded) {
this.#relays = JSON.parse(loaded);
} else {
this.#relays = ["wss://nos.lol/", "wss://relay.damus.io/", "wss://relay.nostr.band/"];
this.#save();
}
}
add(u: string) {
const url = sanitizeRelayUrl(u);
if (url) {
this.#relays = appendDedupe(this.#relays, [url]);
this.#save();
}
}
remove(u: string) {
const url = sanitizeRelayUrl(u);
if (url) {
this.#relays = this.#relays.filter((a) => a !== url);
this.#save();
}
}
#save() {
localStorage.setItem(storageKey, JSON.stringify(this.#relays));
this.notifyChange();
}
takeSnapshot(): string[] {
return [...this.#relays];
}
}
const relayStore = new RelaysStore();
export function useRelays() {
const relays = useSyncExternalStore(
(s) => relayStore.hook(s),
() => relayStore.snapshot(),
);
return {
relays,
add: (a: string) => relayStore.add(a),
remove: (a: string) => relayStore.remove(a),
};
}

View File

@ -62,8 +62,5 @@ export async function initSystem() {
System.Init(), System.Init(),
]; ];
for (const r of ["wss://nos.lol", "wss://relay.damus.io", "wss://relay.nostr.band"]) {
System.ConnectToRelay(r, { read: true, write: true });
}
await Promise.all(tasks); await Promise.all(tasks);
} }