- commit
- b143f053edbecd7564ba2738ca96587d98f8da80
- parent
- 8745d13982ee67d67ac5c78cb7c5f6ba65c76075
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 5 | ++-- |
| M | test/ | 39 | ++++++++++++++++++++---------- |
| M | test/ | 4 | +++ |
@@ -69,7 +69,7 @@ export interface Env {
6969 // identity; 62 is request-to-vanish (store.vanish); 67 is the EOSE hint7070 // array at the end of the subscription handler; 70 is the "-" tag check in7171 // gate(); 77 is negentropy sync (handleSync).72-export const SUPPORTED_NIPS = [1, 5, 9, 11, 13, 17, 29, 40, 42, 45, 50, 56, 59, 62, 67, 70, 77, 86, 98];72+export const SUPPORTED_NIPS = [1, 5, 9, 11, 13, 17, 29, 40, 42, 45, 46, 50, 56, 59, 62, 67, 70, 77, 86, 98];7373 export const KIND_REPORT = 1984;7474 // NIP-46 remote signing traffic: ephemeral, encrypted end to end, never stored.7575 export const KIND_NOSTR_CONNECT = 24133;@@ -1063,7 +1063,8 @@ export class Relay extends DurableObject<Env> {
10631063 max_limit: p.maxLimit,10641064 default_limit: p.maxLimit,10651065 max_subid_length: 64,1066- auth_required: p.reads === "auth",1066+ // True whenever a fresh socket cannot REQ: reads for the authenticated or for members.1067+ auth_required: p.reads !== "open",10671068 payment_required: false,10681069 restricted_writes: p.writes !== "open" || this.settings.isUnclaimed(),10691070 created_at_upper_limit: p.maxFuture || undefined,@@ -18,6 +18,23 @@ async function rpc(host: string, sk: Uint8Array, method: string, ...params: unkn
1818 }1919 const get = (host: string, path: string) => SELF.fetch(`http://${host}${path}`);2020 21+// scan rasterizes a module grid and reads it with jsQR, a decoder that22+// shares nothing with the encoder, so it proves a phone can read it.23+function scan(size: number, dark: (x: number, y: number) => boolean): string | undefined {24+ const px = 4, margin = 4, n = size + margin * 2, w = n * px;25+ const img = new Uint8ClampedArray(w * w * 4).fill(255);26+ for (let y = 0; y < size; y++) {27+ for (let x = 0; x < size; x++) {28+ if (!dark(x, y)) continue;29+ for (let dy = 0; dy < px; dy++) for (let dx = 0; dx < px; dx++) {30+ const i = (((y + margin) * px + dy) * w + (x + margin) * px + dx) * 4;31+ img[i] = img[i + 1] = img[i + 2] = 0;32+ }33+ }34+ }35+ return jsQR(img, w, w)?.data;36+}37+2138 // readBack decodes a symbol without any error correction: it must be exact.2239 function readBack(sym: ReturnType<typeof encode>): { mask: number; level: number; bytes: Uint8Array } {2340 const { size, modules, version } = sym;@@ -113,19 +130,7 @@ describe("qr encoder", () => {
113130 const full = Array.from({ length: 20 }, (_, i) => Array.from({ length: capacity(i + 1) }, (_, j) => String.fromCharCode(48 + ((j * 7 + i) % 74))).join(""));114131 for (const text of ["nostr:npub1abc", uri, ...full]) {115132 const sym = encode(text);116- const px = 4, margin = 4, n = sym.size + margin * 2, w = n * px;117- const img = new Uint8ClampedArray(w * w * 4).fill(255);118- for (let y = 0; y < sym.size; y++) {119- for (let x = 0; x < sym.size; x++) {120- if (!sym.modules[y * sym.size + x]) continue;121- for (let dy = 0; dy < px; dy++) for (let dx = 0; dx < px; dx++) {122- const i = (((y + margin) * px + dy) * w + (x + margin) * px + dx) * 4;123- img[i] = img[i + 1] = img[i + 2] = 0;124- }125- }126- }127- const r = jsQR(img, w, w);128- expect(r?.data, `version ${sym.version}`).toBe(text);133+ expect(scan(sym.size, (x, y) => sym.modules[y * sym.size + x] === 1), `version ${sym.version}`).toBe(text);129134 }130135 });131136 });@@ -185,6 +190,14 @@ describe("relay card", () => {
185190 expect(svg).toContain("members write");186191 expect(svg).toContain("group naddr");187192 expect(svg).toContain('<path d="M');193+ // The QR on the card scans to the naddr: the card scales the modules194+ // into a 150 unit box, so read the cells back from the path.195+ const path = svg.match(/<g transform="translate\(418 58\)">.*?<path d="([^"]+)"/)![1];196+ const cells = [...path.matchAll(/M([\d.]+) ([\d.]+)h([\d.]+)/g)].map((m) => ({ x: +m[1], y: +m[2], cell: +m[3] }));197+ const size = encode(c.naddr).size;198+ expect(cells.length).toBeGreaterThan(100);199+ const dark = new Set(cells.map((k) => Math.round(k.x / k.cell) + "," + Math.round(k.y / k.cell)));200+ expect(scan(size, (x, y) => dark.has(x + "," + y))).toBe(c.naddr);188201 });189202 190203 it("says so on unclaimed and leased relays, and does not sign for them", async () => {@@ -139,6 +139,10 @@ describe("NIP-46 transport", () => {
139139 expect(await c.open("c", { kinds: [24133, 1] })).toMatch(/^auth-required/);140140 expect(await c.open("d", { kinds: [24133] }, { kinds: [1] })).toMatch(/^auth-required/);141141 expect(await c.open("e", {})).toMatch(/^auth-required/);142+ // NIP-11 says so, and advertises the transport.143+ const info: any = await (await SELF.fetch(`http://${host}/`, { headers: { accept: "application/nostr+json" } })).json();144+ expect(info.limitation.auth_required).toBe(true);145+ expect(info.supported_nips).toContain(46);142146 // Amber's probe before it lists a relay: a throwaway key, no AUTH, a143147 // subscription naming the key, and its own event echoed back.144148 const probe = generateSecretKey();