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

View File

@ -3,9 +3,35 @@ import { Button } from "../element/button";
import { LoginSession, LoginState, useLogin } from "../login";
import { ProfileImage } from "../element/profile-image";
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() {
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() {
if ("nostr" in window) {
@ -18,14 +44,18 @@ export function Layout() {
return (
<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">
<img src="/logo_256.jpg" className="rounded-full" height={40} width={40} />
<h1 className="font-bold uppercase">dtan.xyz</h1>
</Link>
<div className="w-1/2">
<div className="w-1/3">
<Search />
</div>
<div className="grow"></div>
<Link to="/relays">
<Button type="secondary">Relays</Button>
</Link>
{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(),
];
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);
}