diff options
| author | Adam Soltys <[email protected]> | 2026-10-19 18:10:31 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-19 18:10:31 -0700 |
| commit | 1a85347b0a37886e550497eab5ece05410fce923 (patch) | |
| tree | af4b5497eb89c5b6063ba772d8974f8057ec6b13 /tools | |
| parent | 762bdcb558af94b540801a89cd16cd419d3a27d5 (diff) | |
| download | coinosv3-1a85347b0a37886e550497eab5ece05410fce923.tar.gz coinosv3-1a85347b0a37886e550497eab5ece05410fce923.zip | |
Posts: a relay named in a post (wss://…) is a link to what's on it — the raw firehose of that relay alone (/feed/all?r=…). Trailing punctuation stays outside the link.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/relay-link-test.js | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/tools/relay-link-test.js b/tools/relay-link-test.js new file mode 100644 index 0000000..52984ad --- /dev/null +++ b/tools/relay-link-test.js @@ -0,0 +1,66 @@ +// A relay named in a post (wss://…) is a link: tapping it opens the raw +// firehose of that relay (/feed/all?r=…), read from that relay alone. +// Run: bun tools/relay-link-test.js +import assert from 'node:assert/strict'; +import puppeteer from './puppeteer.js'; +import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure'; +import { buildHtml, buildVerifyWorker } from '../build.js'; + +const sk = generateSecretKey(), now = Math.floor(Date.now() / 1000); +const note = finalizeEvent({ kind: 1, created_at: now, tags: [], content: 'I run a good relay to read directly:\n\nwss://theforest.nostr1.com.\n\nAnyone can read from it.' }, sk); +const forest = finalizeEvent({ kind: 1, created_at: now - 5, tags: [], content: 'Hello from the forest relay, one of its own posts.' }, generateSecretKey()); +const profile = finalizeEvent({ kind: 0, created_at: now, tags: [], content: JSON.stringify({ name: 'Relay Admin' }) }, sk); +const html = await buildHtml({ minify: true, pwa: false }); +const worker = await buildVerifyWorker({ minify: true }); +const server = Bun.serve({ port: 0, fetch(req) { + const path = new URL(req.url).pathname; + if (path === '/api/feed') return Response.json({ version: 1, generatedAt: Date.now(), popUntil: now - 100, notes: [note], profiles: [profile] }); + if (path === '/verify-worker.js') return new Response(worker, { headers: { 'Content-Type': 'text/javascript' } }); + if (path.startsWith('/punks')) return new Response(null, { status: 404 }); + return new Response(html, { headers: { 'Content-Type': 'text/html' } }); +} }); +const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); +const check = (name, ok, detail = '') => { assert(ok, name + (detail ? ' — ' + detail : '')); console.log(' ✓ ' + name); }; +try { + const page = await browser.newPage(); + const errors = []; + page.on('pageerror', (e) => errors.push(e.message)); + await page.setRequestInterception(true); + page.on('request', (r) => (r.url().startsWith(server.url.origin) || r.url().startsWith('data:') ? r.continue() : r.abort())); + await page.evaluateOnNewDocument((forest) => { + window.asked = []; + class FakeSocket extends EventTarget { + static OPEN = 1; static CLOSED = 3; + readyState = 0; + constructor(url) { super(); this.url = url; setTimeout(() => { this.readyState = 1; this.onopen?.({}); this.dispatchEvent(new Event('open')); }, 0); } + emit(m) { const e = new MessageEvent('message', { data: JSON.stringify(m) }); this.onmessage?.(e); this.dispatchEvent(e); } + send(data) { + const m = JSON.parse(data); + if (m[0] !== 'REQ') return; + window.asked.push({ url: this.url, filter: m[2] }); + setTimeout(() => { + if (/theforest/.test(this.url) && m[2].kinds?.includes(1) && !m[2].ids && !m[2].authors) this.emit(['EVENT', m[1], forest]); + this.emit(['EOSE', m[1]]); + }, 0); + } + close() { this.readyState = 3; this.onclose?.({}); } + } + window.WebSocket = FakeSocket; + }, forest); + await page.goto(server.url.origin + '/feed'); + await page.waitForFunction(() => document.body.innerText.includes('I run a good relay'), { timeout: 5000 }); + const link = await page.$$eval('.notes-feed a', (as) => as.filter((a) => /theforest/.test(a.textContent)).map((a) => ({ text: a.textContent, href: a.getAttribute('href') }))); + check('the relay in the post is a link', link.length === 1, JSON.stringify(link)); + check('...without the full stop after it', link[0].text === 'wss://theforest.nostr1.com' && /theforest\.nostr1\.com\./.test(await page.evaluate(() => document.querySelector('.notes-feed').innerText))); + check('...to that relay\'s firehose', link[0].href === '/feed/all?r=' + encodeURIComponent('wss://theforest.nostr1.com'), link[0].href); + await page.evaluate(() => [...document.querySelectorAll('.notes-feed a')].find((a) => /theforest/.test(a.textContent)).click()); + check('tapping it shows the relay\'s own posts', await page.waitForFunction(() => document.body.innerText.includes('Hello from the forest relay'), { timeout: 8000 }).then(() => true, () => false)); + const forestAsks = await page.evaluate(() => window.asked.filter((a) => /theforest/.test(a.url) && a.filter.kinds?.includes(1) && !a.filter.authors && !a.filter.ids)); + check('...asked of that relay, for everything on it', forestAsks.length > 0); + check('...and the address bar says so', decodeURIComponent(await page.evaluate(() => location.pathname + location.search)) === '/feed/all?r=wss://theforest.nostr1.com', await page.evaluate(() => location.href)); + check('no page errors', !errors.length, errors.join(' | ')); + console.log('\n✅ relay links open the relay'); +} finally { + await browser.close(); + server.stop(true); +} |
