aboutsummaryrefslogtreecommitdiffstats
path: root/src/recipient-search.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-02 18:56:04 -0700
committerAdam Soltys <[email protected]>2026-09-02 18:56:04 -0700
commitc4357ab5563aacc3614bfaf8f54cfb5c79a7f1ba (patch)
tree242d39680e551d034fae6576a8ddc7c8f67cb12e /src/recipient-search.js
parentd5e590e655c989cb855328e28349df2129adcd18 (diff)
downloadcoinosv3-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.js57
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';