clone

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

main
nobel / neon / neon.js
neon.js 3.9K
1(()=>{2 let g = globalThis, d = g.document3 if (g.neon) return4 let A = "qpzry9x8gf2tvdw0s3jn54khce6mua7l", te = new TextEncoder(), td = new TextDecoder()5 let hex = (b)=>[...b].map((x)=>x.toString(16).padStart(2, "0")).join("")6 let bytes = (h)=>h.match(/../g).map((x)=>parseInt(x, 16))7 let poly = (v, c = 1)=>{8 for (let x of v){9 let b = c >> 2510 c = (c & 0x1ffffff) << 5 ^ x11 for (let i = 0; i < 5; i++) if (b >> i & 1) c ^= [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3][i]12 }13 return c14 }15 let hrp = (h)=>[...h].map((c)=>c.charCodeAt() >> 5).concat(0, [...h].map((c)=>c.charCodeAt() & 31))16 let conv = (data, f, t, pad)=>{17 let a = 0, b = 0, o = [], m = (1 << t) - 118 for (let x of data){19 a = (a << f | x) & 409520 for (b += f; b >= t;) o.push(a >> (b -= t) & m)21 }22 if (pad && b) o.push(a << t - b & m)23 if (!pad && (b >= f || a << t - b & m)) throw Error("bad padding")24 return o25 }26 let tlv = (t, v)=>[t, v.length, ...v]27 let encode = (type, data)=>{28 let o = data29 if (typeof data == "string") o = bytes(data)30 else {31 let u = (s)=>[...te.encode(s)], naddr = type == "naddr"32 o = tlv(0, naddr ? u(data.identifier) : bytes(data.id ?? data.pubkey))33 for (let r of data.relays ?? []) o.push(...tlv(1, u(r)))34 let a = naddr ? data.pubkey : data.author, k = data.kind35 if (a) o.push(...tlv(2, bytes(a)))36 if (k != null) o.push(...tlv(3, [k >>> 24, k >> 16 & 255, k >> 8 & 255, k & 255]))37 }38 let w = conv(o, 8, 5, 1), p = poly([...hrp(type), ...w, 0, 0, 0, 0, 0, 0]) ^ 139 for (let i = 0; i < 6; i++) w.push(p >> 5 * (5 - i) & 31)40 return type + "1" + w.map((x)=>A[x]).join("")41 }42 let decode = (s)=>{43 s = s.replace(/^nostr:/, "").toLowerCase()44 let i = s.lastIndexOf("1"), type = s.slice(0, i), w = [...s.slice(i + 1)].map((c)=>A.indexOf(c))45 if (i < 1 || w.length < 6 || w.includes(-1) || poly([...hrp(type), ...w]) != 1) throw Error("bad bech32")46 let b = conv(w.slice(0, -6), 5, 8)47 if (!/^n(profile|event|addr)$/.test(type)) return {type, data:hex(b)}48 let data = {relays:[]}, naddr = type == "naddr"49 for (let j = 0; j < b.length; j += 2 + b[j + 1]){50 let t = b[j], v = b.slice(j + 2, j + 2 + b[j + 1]), str = td.decode(new Uint8Array(v))51 if (t == 0) naddr ? data.identifier = str : data[type == "nevent" ? "id" : "pubkey"] = hex(v)52 if (t == 1) data.relays.push(str)53 if (t == 2) data[naddr ? "pubkey" : "author"] = hex(v)54 if (t == 3) data.kind = v.reduce((a, x)=>a * 256 + x, 0)55 }56 return {type, data}57 }58 let esc = (s)=>String(s).replace(/[&<>"']/g, (c)=>`&#${c.charCodeAt()};`)59 let link = (ref)=>(g.nneCfg?.link ?? "https://njump.me/") + ref60 let rich = (s)=>esc(s).replace(/\bhttps?:\/\/[^\s<]*[^\s<.,:!?)]|nostr:(n(?:pub|profile|ote|event|addr)1[a-z0-9]+)/g, (m, ref)=>{61 if (!ref) return /\.(png|jpe?g|gif|webp|avif)(\?|$)/i.test(m) ? `<img src="${m}" loading="lazy" alt="">` : `<a href="${m}" target="_blank" rel="noopener">${m}</a>`62 let who = /^np/.test(ref) ? ` nrn-profile="${ref}" nrn-field="name"` : ""63 return `<a href="${link(ref)}"${who}>${ref.slice(0, 12)}…</a>`64 })65 let ago = (t)=>{66 let s = Date.now() / 1e3 - t67 return s < 60 ? "now" : s < 3600 ? (s / 60 | 0) + "m" : s < 86400 ? (s / 3600 | 0) + "h" : s < 604800 ? (s / 86400 | 0) + "d" : new Date(t * 1e3).toLocaleDateString()68 }69 let tick = (n)=>n.querySelectorAll?.("[nne-ago]").forEach((e)=>e.textContent = ago(e.getAttribute("nne-ago")))70 g.neon = {encode, decode, rich, ago, esc}71 if (!d) return72 d.addEventListener("nhy:event", (evt)=>{73 let {event:e, fields:f} = evt.detail, k = e.kind74 f.content = rich(e.content)75 f.npub = encode("npub", e.pubkey)76 f.note = encode("note", e.id)77 f.nevent = encode("nevent", {id:e.id, author:e.pubkey, kind:k})78 if (k >= 3e4 && k < 4e4) f.naddr = encode("naddr", {identifier:e.tags.find((t)=>t[0] == "d")?.[1] ?? "", pubkey:e.pubkey, kind:k})79 f.ago = ago(e.created_at)80 })81 new MutationObserver((recs)=>recs.forEach((r)=>r.addedNodes.forEach(tick))).observe(d, {childList:1, subtree:1})82 setInterval(()=>tick(d), 3e4)83})()