clone

tiny dependency-free nostr libraries for plain html, each under 5kb and named for an element. public domain

main
nobel / radon / radon.js
radon.js 3.1K
1(()=>{2 let d = document3 if (d.__nrn_mo) return4 let cache = {}, watch = {}, waits = {}, pending = new Set(), relays = new Set(), timer, nips = {}5 let send = (el, type, detail)=>el.dispatchEvent(new CustomEvent("nrn:" + type, {detail, cancelable:1, bubbles:1, composed:1}))6 let hexOf = (s)=>{7 if (/^[0-9a-f]{64}$/.test(s)) return s8 try { let x = neon.decode(s).data; return x.pubkey ?? x } catch {}9 }10 let nip05 = (id, pk)=>{11 let [name, host] = id.includes("@") ? id.split("@") : ["_", id]12 return nips[id + pk] ??= fetch(`https://${host}/.well-known/nostr.json?name=${encodeURIComponent(name)}`)13 .then((r)=>r.json()).then((j)=>j.names?.[name] == pk, ()=>false)14 }15 let fill = (el, pk, p)=>{16 if (!send(el, "profile", {pubkey:pk, profile:p})) return17 for (let e of [el, ...el.querySelectorAll("[nrn-field]")]){18 let f = e.getAttribute("nrn-field"), v = p[f]19 if (e != el && e.closest("[nrn-profile]") != el || v == null || typeof v == "object") continue20 e.matches("img") ? e.src = v : e.textContent = v21 if (f == "nip05") nip05(v, pk).then((ok)=>el.toggleAttribute("nrn-verified", ok))22 }23 }24 let update = (pk)=>{25 watch[pk]?.forEach((el)=>el.isConnected ? fill(el, pk, cache[pk]) : watch[pk].delete(el))26 waits[pk]?.forEach((r)=>r(cache[pk]))27 delete waits[pk]28 }29 let ingest = (e)=>{30 if (e.kind || cache[e.pubkey]?.created_at >= e.created_at) return31 try {32 let p = JSON.parse(e.content)33 p.name = p.display_name || p.name || p.displayName34 cache[e.pubkey] = {...p, created_at:e.created_at}35 update(e.pubkey)36 } catch {}37 }38 let flush = ()=>{39 timer = 040 let authors = [...pending], rs = [...relays, ...window.nrnCfg?.relays ?? ["wss://purplepag.es"]], eose = 041 pending.clear()42 relays.clear()43 let end = ()=>{44 c?.close()45 authors.forEach((pk)=>waits[pk] && update(pk))46 }47 let c = hydrogen.req({48 relays:rs,49 filters:[{kinds:[0], authors}],50 onevent:ingest,51 oneose:(u)=>rs.includes(u) && ++eose == rs.length && end()52 })53 setTimeout(end, 8e3)54 }55 let want = (pk, el)=>{56 pending.add(pk)57 hydrogen.relays(el).forEach((u)=>relays.add(u))58 timer ||= setTimeout(flush, 50)59 }60 let get = (pk)=>cache[pk = hexOf(pk)] ? Promise.resolve(cache[pk]) : new Promise((r)=>{61 (waits[pk] ??= []).push(r)62 want(pk)63 })64 let init = (el)=>{65 let pk = hexOf(el.getAttribute("nrn-profile"))66 if (el.__nrn == pk || !pk || el.closest("[nrn-ignore]")) return67 watch[el.__nrn]?.delete(el)68 el.__nrn = pk;69 (watch[pk] ??= new Set()).add(el)70 cache[pk] ? fill(el, pk, cache[pk]) : want(pk, el)71 }72 let process = (n)=>{73 if (n.matches?.("[nrn-profile]")) init(n)74 n.querySelectorAll?.("[nrn-profile]").forEach(init)75 }76 window.radon = {get, cache}77 d.__nrn_mo = new MutationObserver((recs)=>recs.forEach((r)=>r.type == "attributes" ? init(r.target) : r.addedNodes.forEach(process)))78 d.addEventListener("nhy:event", (evt)=>ingest(evt.detail.event))79 d.addEventListener("nxe:publish", (evt)=>ingest(evt.detail.cfg.event))80 let boot = ()=>{81 d.__nrn_mo.observe(d.documentElement, {childList:1, subtree:1, attributes:1, attributeFilter:["nrn-profile"]})82 process(d.body)83 }84 d.readyState == "loading" ? d.addEventListener("DOMContentLoaded", boot) : boot()85})()