clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

dependabot/npm_and_yarn/npm-f01a459b0f
bindws / test / object / relay.test.ts
relay.test.ts 4.6K
1// The relay itself: claiming a name over NIP-86 with NIP-98 tokens, the2// expiry alarm, and the Worker's routing by hostname.3import { SELF, env, runInDurableObject } from "cloudflare:test";4import { describe, it, expect } from "vitest";5import { finalizeEvent, generateSecretKey, getPublicKey } from "nostr-tools/pure";6import { getToken } from "nostr-tools/nip98";7import type { Relay } from "../../src/relay.ts";8import { now, ev, rpc } from "../helpers/relay.ts";9import { WS } from "../helpers/ws.ts";1011describe("claiming", () => {12 it("starts unclaimed: reads work, writes are refused, management needs a claim", async () => {13 const host = "alpha.bind.ws";14 const sk = generateSecretKey();15 const c = await WS.connect(host);16 expect(await c.req({ kinds: [1] })).toEqual([]);17 const r = await c.ok(ev(sk, 1, "hi"));18 expect(r.ok).toBe(false);19 expect(r.msg).toMatch(/^restricted: .*unclaimed/);2021 const info = await (await SELF.fetch(`http://${host}/`, { headers: { accept: "application/nostr+json" } })).json<any>();22 expect(info.name).toBe("alpha");23 expect(info.limitation.restricted_writes).toBe(true);24 expect(info.supported_nips).toContain(86);2526 expect((await rpc(host, sk, "stats")).status).toBe(403);27 expect((await rpc(host, null, "claim")).status).toBe(401);28 const claim = await rpc(host, sk, "claim");29 expect(claim.result).toEqual({ owner: getPublicKey(sk), claimed: true });30 const other = generateSecretKey();31 expect((await rpc(host, other, "claim")).status).toBe(403);32 expect((await rpc(host, sk, "claim")).result.claimed).toBe(true);3334 expect((await c.ok(ev(sk, 1, "hi"))).ok).toBe(true);35 expect((await rpc(host, sk, "stats")).result.events).toBe(10); // the note, the relay-signed roster, profile, discovery record, two role definitions and the four NIP-29 state events36 const info2 = await (await SELF.fetch(`http://${host}/`, { headers: { accept: "application/nostr+json" } })).json<any>();37 expect(info2.pubkey).toBe(getPublicKey(sk));38 expect(info2.limitation.restricted_writes).toBe(false);39 });4041 it("rejects NIP-98 tokens for the wrong URL, method, or body", async () => {42 const host = "beta.bind.ws";43 const sk = generateSecretKey();44 const url = `http://${host}/`;45 const body = JSON.stringify({ method: "claim", params: [] });46 const post = (authorization: string) =>47 SELF.fetch(url, { method: "POST", headers: { "content-type": "application/nostr+json+rpc", authorization }, body });48 const bad = await getToken("http://elsewhere.bind.ws/", "POST", (e) => finalizeEvent(e, sk), true, { method: "claim", params: [] });49 expect((await post(bad)).status).toBe(401);50 const wrongBody = await getToken(url, "POST", (e) => finalizeEvent(e, sk), true, { method: "stats", params: [] });51 expect((await post(wrongBody)).status).toBe(401);52 expect((await post("Nostr notbase64")).status).toBe(401);53 const good = await getToken(url, "POST", (e) => finalizeEvent(e, sk), true, { method: "claim", params: [] });54 expect((await post(good)).status).toBe(200);55 });56});5758describe("expiry alarm", () => {59 it("sweeps expired events from storage", async () => {60 const stub = env.RELAY.getByName("epsilon");61 const sk = generateSecretKey();62 const t = now();63 await runInDurableObject(stub, async (instance: Relay) => {64 instance.settings.update({ owner: getPublicKey(sk) });65 const e = ev(sk, 1, "temporary", [["expiration", String(t + 5)]], t);66 expect(instance.accept(e, null).ok).toBe(true);67 expect(instance.store.stats().events).toBe(1);68 expect(instance.store.sweepExpired(t + 1)).toBe(t + 5);69 expect(instance.store.stats().events).toBe(1);70 expect(instance.store.sweepExpired(t + 6)).toBe(0);71 expect(instance.store.stats().events).toBe(0);72 });73 });74});7576describe("worker routing", () => {77 it("serves the apex, rejects bad names, and isolates relays by name", async () => {78 const apex = await SELF.fetch("http://bind.ws/");79 expect(apex.status).toBe(200);80 expect(await apex.text()).toContain("Relay on demand. Sign once, and it's yours.");81 const bad = await SELF.fetch("http://x.bind.ws/", { redirect: "manual" });82 expect(bad.status).toBe(302);83 const sk = generateSecretKey();84 await rpc("one.bind.ws", sk, "claim");85 const a = await WS.connect("one.bind.ws");86 expect((await a.ok(ev(sk, 1, "only here"))).ok).toBe(true);87 const b = await WS.connect("two.bind.ws");88 expect(await b.req({ kinds: [1] })).toEqual([]);89 const page = await SELF.fetch("http://one.bind.ws/");90 expect(page.headers.get("content-type")).toContain("text/html");91 });92});