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 / card.test.ts
card.test.ts 4.8K
1// The relay card: the JSON, the nostr event, the SVG with a QR the phone2// can read, and the caching. The encoder itself is test/unit/qr.test.ts.3import { SELF } from "cloudflare:test";4import { describe, it, expect } from "vitest";5import { generateSecretKey, getPublicKey, verifyEvent } from "nostr-tools/pure";6import { decode } from "nostr-tools/nip19";7import { encode } from "../../src/qr.ts";8import { scan } from "../helpers/qr.ts";9import { rpc, get } from "../helpers/relay.ts";1011describe("relay card", () => {12 it("describes a claimed relay, hides the member count when the directory is private, and signs", async () => {13 const host = "cardy.bind.ws";14 const owner = generateSecretKey();15 const member = generateSecretKey();16 await rpc(host, owner, "claim");17 await rpc(host, owner, "setmember", getPublicKey(member), { name: "bob" });18 await rpc(host, owner, "setpolicy", { description: "A relay for people who like cards.", writes: "allowlist" });1920 let r = await get(host, "/card.json");21 expect(r.status).toBe(200);22 expect(r.headers.get("cache-control")).toContain("max-age=300");23 let c: any = await r.json();24 expect(c.state).toBe("claimed");25 expect(c.name).toBe("cardy");26 expect(c.url).toBe("wss://cardy.bind.ws");27 expect(c.console).toBe("https://cardy.bind.ws/");28 expect(c.owner).toBe(getPublicKey(owner));29 expect(c.members).toBe(2);30 expect(c.writes).toBe("allowlist");31 expect(c.reads).toBe("open");32 expect(c.fuel).toBe("allowance");33 expect(c.self).toMatch(/^[0-9a-f]{64}$/);34 expect(c.signed_url).toBe("https://cardy.bind.ws/card.nostr");35 const prof = decode(c.nprofile);36 expect(prof.type).toBe("nprofile");37 expect((prof.data as any).pubkey).toBe(c.owner);38 expect((prof.data as any).relays).toEqual(["wss://cardy.bind.ws"]);39 const addr = decode(c.naddr);40 expect(addr.type).toBe("naddr");41 expect((addr.data as any).kind).toBe(39000);42 expect((addr.data as any).pubkey).toBe(c.self);43 expect((addr.data as any).identifier).toBe("cardy");44 expect((addr.data as any).relays).toEqual(["wss://cardy.bind.ws"]);4546 await rpc(host, owner, "setpolicy", { directoryPublic: false });47 c = await (await get(host, "/card.json")).json();48 expect(c.members).toBeUndefined();4950 const signed: any = await (await get(host, "/card.nostr")).json();51 expect(signed.kind).toBe(30078);52 expect(signed.pubkey).toBe(c.self);53 expect(signed.tags).toEqual([["d", "bind.ws/card"]]);54 expect(verifyEvent(signed)).toBe(true);55 expect(JSON.parse(signed.content).naddr).toBe(c.naddr);5657 r = await get(host, "/card.svg");58 expect(r.status).toBe(200);59 expect(r.headers.get("content-type")).toContain("image/svg+xml");60 const svg = await r.text();61 expect(svg).toContain("cardy");62 expect(svg).toContain("people who like cards");63 expect(svg).toContain("members write");64 expect(svg).toContain("group naddr");65 expect(svg).toContain('<path d="M');66 // The QR on the card scans to the naddr: the card scales the modules67 // into a 150 unit box, so read the cells back from the path.68 const path = svg.match(/<g transform="translate\(418 58\)">.*?<path d="([^"]+)"/)![1];69 const cells = [...path.matchAll(/M([\d.]+) ([\d.]+)h([\d.]+)/g)].map((m) => ({ x: +m[1], y: +m[2], cell: +m[3] }));70 const size = encode(c.naddr).size;71 expect(cells.length).toBeGreaterThan(100);72 const dark = new Set(cells.map((k) => Math.round(k.x / k.cell) + "," + Math.round(k.y / k.cell)));73 expect(scan(size, (x, y) => dark.has(x + "," + y))).toBe(c.naddr);74 });7576 it("says so on unclaimed and leased relays, and does not sign for them", async () => {77 const host = "nobody.bind.ws";78 const c: any = await (await get(host, "/card.json")).json();79 expect(c.state).toBe("unclaimed");80 expect(c.owner).toBeUndefined();81 expect(c.naddr).toBeUndefined();82 expect((await get(host, "/card.nostr")).status).toBe(404);83 const svg = await (await get(host, "/card.svg")).text();84 expect(svg).toContain("Nobody owns this relay yet");85 const l: any = await (await SELF.fetch("http://bind.ws/lease", { method: "POST", headers: { "cf-connecting-ip": "10.9.9.9" } })).json();86 const lc: any = await (await get(`${l.name}.bind.ws`, "/card.json")).json();87 expect(lc.state).toBe("leased");88 expect(lc.expires_at).toBe(l.expires_at);89 });9091 it("serves any short text as a QR and caps it", async () => {92 const host = "nobody.bind.ws";93 let r = await get(host, "/qr.svg?text=" + encodeURIComponent("nostr:npub1abc"));94 expect(r.status).toBe(200);95 expect(r.headers.get("content-type")).toContain("image/svg+xml");96 expect(await r.text()).toContain("<path");97 expect((await get(host, "/qr.svg")).status).toBe(400);98 r = await get(host, "/qr.svg?text=" + "a".repeat(513));99 expect(r.status).toBe(413);100 });101});