clone

tiny dependency-free nostr libraries for plain html, each under 5kb and named for an element. public domain

main
nobel / server.ts
server.ts 2.5K
1// Dev server: static files plus a tiny in-memory nostr relay at /relay.2// The relay reuses helium.js to check signatures and argon.js to match filters.3import { serveDir } from "jsr:@std/http@1/file-server";4import "./helium/helium.js";5import "./argon/argon.js";67type Event = { id: string; pubkey: string; kind: number; created_at: number; tags: string[][]; content: string; sig: string };8type Filter = Record<string, unknown> & { limit?: number };9// deno-lint-ignore no-explicit-any10const { helium, argon } = globalThis as any;1112export function serve(port = 8000) {13 const events: Event[] = [];14 const clients = new Set<{ ws: WebSocket; subs: Map<string, Filter[]> }>();1516 const relay = (req: Request) => {17 const { socket: ws, response } = Deno.upgradeWebSocket(req);18 const client = { ws, subs: new Map<string, Filter[]>() };19 const send = (m: unknown[]) => ws.readyState === 1 && ws.send(JSON.stringify(m));20 clients.add(client);21 ws.onclose = () => clients.delete(client);22 ws.onmessage = async (m) => {23 let msg;24 try { msg = JSON.parse(m.data); } catch { return; }25 const [type, a, ...filters] = msg;26 if (type === "REQ") {27 client.subs.set(a, filters);28 const newest = [...events].reverse(), out = new Map<string, Event>();29 for (const f of filters) {30 newest.filter((e) => argon.match(f, e)).slice(0, f.limit ?? Infinity).forEach((e) => out.set(e.id, e));31 }32 out.forEach((e) => send(["EVENT", a, e]));33 send(["EOSE", a]);34 } else if (type === "CLOSE") {35 client.subs.delete(a);36 } else if (type === "EVENT") {37 const ok = await helium.check(a);38 send(["OK", a?.id, ok, ok ? "" : "invalid: bad id or signature"]);39 if (!ok || events.some((e) => e.id === a.id)) return;40 if (a.kind < 20000 || a.kind >= 30000) events.push(a);41 for (const c of clients) {42 for (const [id, fs] of c.subs) {43 if (fs.some((f) => argon.match(f, a))) c.ws.send(JSON.stringify(["EVENT", id, a]));44 }45 }46 }47 };48 return response;49 };5051 return Deno.serve({ port, onListen: ({ port }) => console.log(`nobel: http://localhost:${port}/demo/`) }, (req) => {52 if (new URL(req.url).pathname === "/relay" && req.headers.get("upgrade") === "websocket") return relay(req);53 return serveDir(req, { fsRoot: new URL(".", import.meta.url).pathname, quiet: true });54 });55}5657if (import.meta.main) serve(Number(Deno.env.get("PORT") ?? 8000));