diff options
| author | Adam Soltys <[email protected]> | 2026-09-02 18:56:04 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-02 18:56:04 -0700 |
| commit | c4357ab5563aacc3614bfaf8f54cfb5c79a7f1ba (patch) | |
| tree | 242d39680e551d034fae6576a8ddc7c8f67cb12e /src/recipient-search.js | |
| parent | d5e590e655c989cb855328e28349df2129adcd18 (diff) | |
| download | coinosv3-c4357ab5563aacc3614bfaf8f54cfb5c79a7f1ba.tar.gz coinosv3-c4357ab5563aacc3614bfaf8f54cfb5c79a7f1ba.zip | |
Recipient search warms its pipes on focus, not first keystroke: the Primal cache socket, the registrar's TLS session, and the NIP-50 relay connections each cost a handshake that used to land on the first query — the cold second of dead air. Touching the send recipient field, opening user search, or unfolding New message now pre-opens all three (throttled; the idle reaper still closes the socket a minute after real use)
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
Diffstat (limited to 'src/recipient-search.js')
| -rw-r--r-- | src/recipient-search.js | 57 |
1 files changed, 39 insertions, 18 deletions
diff --git a/src/recipient-search.js b/src/recipient-search.js index bd1767b..a5c5f46 100644 --- a/src/recipient-search.js +++ b/src/recipient-search.js @@ -46,28 +46,49 @@ function primalSearch(q, limit = MAX_RESULTS) { pwsIdle = setTimeout(() => { try { pws?.close(); } catch {} pws = null; }, 60_000); const send = () => { try { pws.send(JSON.stringify(['REQ', sid, { cache: ['user_search', { query: q, limit }] }])); } catch { finish(); } }; if (pws && pws.readyState === 1) return send(); - if (!pws || pws.readyState > 1) { - try { pws = new WebSocket(PRIMAL_CACHE); } catch { pws = null; return finish(); } - pws.onmessage = (ev) => { - try { - const m = JSON.parse(ev.data); - const sub = pwsSubs.get(m[1]); - if (!sub) return; - if (m[0] === 'EVENT' && m[2] && m[2].kind === 0) sub.rows.push(m[2]); - if (m[0] === 'EOSE') sub.finish(); - } catch {} - }; - pws.onerror = pws.onclose = () => { - pws = null; - const waiting = [...pwsSubs.values()]; - pwsSubs.clear(); - for (const s of waiting) s.finish(); - }; - } + if (!ensurePws()) return finish(); pws.addEventListener('open', send, { once: true }); }); } +function ensurePws() { + if (pws && pws.readyState <= 1) return true; + try { pws = new WebSocket(PRIMAL_CACHE); } catch { pws = null; return false; } + pws.onmessage = (ev) => { + try { + const m = JSON.parse(ev.data); + const sub = pwsSubs.get(m[1]); + if (!sub) return; + if (m[0] === 'EVENT' && m[2] && m[2].kind === 0) sub.rows.push(m[2]); + if (m[0] === 'EOSE') sub.finish(); + } catch {} + }; + pws.onerror = pws.onclose = () => { + pws = null; + const waiting = [...pwsSubs.values()]; + pwsSubs.clear(); + for (const s of waiting) s.finish(); + }; + return true; +} + +// Pre-open every search transport BEFORE the first keystroke: the Primal +// socket, the registrar's TLS session, and the NIP-50 relay connections +// each cost a handshake that otherwise lands ON the first query — the +// "cold" second of dead air after typing begins. Call on focus of any +// search-capable field; throttled, and the idle timer still reclaims the +// socket a minute after the last real use. +let warmedAt = 0; +export function warmSearch() { + if (Date.now() - warmedAt < 15_000) return; + warmedAt = Date.now(); + ensurePws(); + clearTimeout(pwsIdle); + pwsIdle = setTimeout(() => { try { pws?.close(); } catch {} pws = null; }, 60_000); + fetch(`${REGISTRAR}/health`).catch(() => {}); + queryOn(SEARCH_RELAYS, { kinds: [0], limit: 1 }, 800).catch(() => {}); +} + // Caches survive reloads: a name searched yesterday paints instantly today. // Profiles cap at 200 by recency; whole-query results keep for 12 hours. const PROF_KEY = 'btc-wallet-search-profiles'; |
