a demonstration replicated social networking web app built with anproto wiredove.net/
social ed25519 protocols

we were re-verifying message integrity on every render, which slowed things way down

+78 -32
+5 -2
adder.js
··· 40 40 Promise.resolve() 41 41 .then(async () => { 42 42 if (!sig) { return } 43 - await render.blob(sig) 43 + await render.blob(sig, { hash: entry?.hash, opened: entry?.opened }) 44 44 const wrapper = entry?.hash ? document.getElementById(entry.hash) : null 45 45 if (wrapper) { 46 46 wrapper.dataset.rendered = 'true' ··· 185 185 if (seen.has(post.hash)) { continue } 186 186 seen.add(post.hash) 187 187 const ts = getTimestamp(post) 188 - entries.push({ hash: post.hash, ts }) 188 + entries.push({ hash: post.hash, ts, opened: post.opened }) 189 189 } 190 190 entries.sort(sortDesc) 191 191 return entries ··· 254 254 updateScrollDirection() 255 255 const div = render.insertByTimestamp(state.container, entry.hash, entry.ts) 256 256 if (!div) { return } 257 + if (entry.opened) { 258 + div.dataset.opened = entry.opened 259 + } 257 260 if (entry.blob) { 258 261 div.dataset.rendering = 'true' 259 262 void scheduleRender(entry, entry.blob)
+7 -4
composer.js
··· 115 115 116 116 if (sig) { 117 117 div.id = hash 118 - await render.blob(signed) 118 + if (opened) { div.dataset.opened = opened } 119 + await render.blob(signed, { hash, opened }) 119 120 } else { 120 121 const scroller = document.getElementById('scroller') 121 122 const opened = await apds.open(signed) 122 123 const ts = opened ? opened.substring(0, 13) : Date.now().toString() 123 124 if (window.__feedEnqueue) { 124 125 const src = window.location.hash.substring(1) 125 - const queued = await window.__feedEnqueue(src, { hash, ts: Number.parseInt(ts, 10), blob: signed }) 126 + const queued = await window.__feedEnqueue(src, { hash, ts: Number.parseInt(ts, 10), blob: signed, opened }) 126 127 if (!queued) { 127 128 const placeholder = render.insertByTimestamp(scroller, hash, ts) 128 129 if (placeholder) { 129 - await render.blob(signed) 130 + if (opened) { placeholder.dataset.opened = opened } 131 + await render.blob(signed, { hash, opened }) 130 132 } 131 133 } 132 134 } else { 133 135 const placeholder = render.insertByTimestamp(scroller, hash, ts) 134 136 if (placeholder) { 135 - await render.blob(signed) 137 + if (opened) { placeholder.dataset.opened = opened } 138 + await render.blob(signed, { hash, opened }) 136 139 } 137 140 } 138 141 overlay.remove()
+44 -21
render.js
··· 66 66 timestampObserver.observe(element) 67 67 } 68 68 69 - const indexReply = (parentHash, replyHash, ts) => { 69 + const indexReply = (parentHash, replyHash, ts, opened = null) => { 70 70 if (!parentHash || !replyHash) { return } 71 71 const list = replyIndex.get(parentHash) || [] 72 72 if (list.some(item => item.hash === replyHash)) { return } 73 - list.push({ hash: replyHash, ts }) 73 + list.push({ hash: replyHash, ts, opened }) 74 74 replyIndex.set(parentHash, list) 75 75 } 76 76 ··· 98 98 } 99 99 void (async () => { 100 100 for (const item of list) { 101 - await appendReply(hash, item.hash, item.ts) 101 + await appendReply(hash, item.hash, item.ts, null, item.opened) 102 102 } 103 103 target.dataset.repliesLoaded = 'true' 104 104 })() ··· 123 123 const parent = yaml.replyHash || yaml.reply || null 124 124 if (!parent) { continue } 125 125 const ts = msg.ts || parseOpenedTimestamp(msg.opened) 126 - indexReply(parent, msg.hash, ts) 126 + indexReply(parent, msg.hash, ts, msg.opened || null) 127 127 } 128 128 } 129 129 ··· 208 208 209 209 const isHash = (value) => typeof value === 'string' && value.length === 44 210 210 211 + const getOpenedFromQuery = async (hash) => { 212 + if (!hash) { return null } 213 + const query = await apds.query(hash) 214 + if (Array.isArray(query) && query[0] && query[0].opened) { 215 + return query[0].opened 216 + } 217 + if (query && query.opened) { 218 + return query.opened 219 + } 220 + return null 221 + } 222 + 211 223 const queueLinkedHashes = async (yaml) => { 212 224 if (!yaml) { return } 213 225 const candidates = new Set() ··· 250 262 replyPreviewCache.set(replyHash, null) 251 263 return null 252 264 } 253 - const opened = await apds.open(signed) 265 + const opened = await getOpenedFromQuery(replyHash) 254 266 if (!opened || opened.length < 14) { 255 267 replyPreviewCache.set(replyHash, null) 256 268 return null ··· 314 326 if (!editHash) { return '' } 315 327 const signed = await apds.get(editHash) 316 328 if (!signed) { return '' } 317 - const opened = await apds.open(signed) 329 + const opened = await getOpenedFromQuery(editHash) 318 330 if (!opened || opened.length < 14) { return '' } 319 331 const content = await apds.get(opened.substring(13)) 320 332 if (!content) { return '' } ··· 386 398 if (!existing && scroller) { 387 399 const signed = await apds.get(targetHash) 388 400 if (signed) { 389 - const opened = await apds.open(signed) 401 + const opened = await getOpenedFromQuery(targetHash) 390 402 const ts = parseOpenedTimestamp(opened) 391 403 insertByTimestamp(scroller, targetHash, ts) 392 404 } ··· 451 463 return yaml.replyHash || yaml.reply || null 452 464 } 453 465 454 - const appendReply = async (parentHash, replyHash, ts, replyBlob = null) => { 466 + const appendReply = async (parentHash, replyHash, ts, replyBlob = null, replyOpened = null) => { 455 467 const wrapper = document.getElementById(parentHash) 456 468 const repliesContainer = wrapper ? wrapper.querySelector('.message-replies') : null 457 469 if (!repliesContainer) { return false } ··· 462 474 replyWrapper = render.hash(replyHash) 463 475 } 464 476 if (!replyWrapper) { return true } 477 + if (replyOpened) { 478 + replyWrapper.dataset.opened = replyOpened 479 + } 465 480 const scroller = document.getElementById('scroller') 466 481 if (scroller && scroller.contains(replyWrapper)) { 467 - await render.blob(blob) 482 + await render.blob(blob, { hash: replyHash, opened: replyOpened }) 468 483 return true 469 484 } 470 485 const replyParent = replyWrapper.parentNode ··· 475 490 replyContain.appendChild(replyWrapper) 476 491 repliesContainer.appendChild(replyContain) 477 492 } 478 - await render.blob(blob) 493 + await render.blob(blob, { hash: replyHash, opened: replyOpened }) 479 494 return true 480 495 } 481 496 ··· 926 941 } 927 942 } 928 943 929 - render.blob = async (blob) => { 930 - const [hash, opened] = await Promise.all([ 931 - apds.hash(blob), 932 - apds.open(blob) 933 - ]) 934 - 935 - const wrapper = document.getElementById(hash) 944 + render.blob = async (blob, meta = {}) => { 945 + let hash = meta.hash || null 946 + let wrapper = hash ? document.getElementById(hash) : null 947 + if (!hash && wrapper) { hash = wrapper.id } 948 + if (!hash) { hash = await apds.hash(blob) } 949 + if (!wrapper && hash) { wrapper = document.getElementById(hash) } 950 + 951 + let opened = meta.opened || (wrapper && wrapper.dataset ? wrapper.dataset.opened : null) 952 + if (!opened && hash) { 953 + opened = await getOpenedFromQuery(hash) 954 + } 955 + if (opened && wrapper && wrapper.dataset && !wrapper.dataset.opened) { 956 + wrapper.dataset.opened = opened 957 + } 958 + 936 959 const div = wrapper && wrapper.classList.contains('message-wrapper') 937 960 ? wrapper.querySelector('.message-shell') 938 961 : wrapper ··· 1016 1039 const authorKey = blob.substring(0, 44) 1017 1040 const replyTo = getReplyParent(yaml) 1018 1041 if (replyTo) { 1019 - indexReply(replyTo, hash, ts) 1042 + indexReply(replyTo, hash, ts, opened) 1020 1043 updateReplyCount(replyTo) 1021 1044 const wrapper = document.getElementById(replyTo) 1022 1045 if (wrapper && wrapper.dataset.repliesLoaded === 'true') { 1023 - await appendReply(replyTo, hash, ts, blob) 1046 + await appendReply(replyTo, hash, ts, blob, opened) 1024 1047 } else if (wrapper) { 1025 1048 observeReplies(wrapper, replyTo) 1026 1049 } ··· 1028 1051 } 1029 1052 if (scroller && (authorKey === src || hash === src || al.includes(authorKey))) { 1030 1053 if (window.__feedEnqueue) { 1031 - const queued = await window.__feedEnqueue(src, { hash, ts, blob }) 1054 + const queued = await window.__feedEnqueue(src, { hash, ts, blob, opened }) 1032 1055 if (queued) { return } 1033 1056 } 1034 1057 return 1035 1058 } 1036 1059 if (scroller && src === '') { 1037 1060 if (window.__feedEnqueue) { 1038 - const queued = await window.__feedEnqueue(src, { hash, ts, blob }) 1061 + const queued = await window.__feedEnqueue(src, { hash, ts, blob, opened }) 1039 1062 if (queued) { return } 1040 1063 } 1041 1064 return
+22 -5
route.js
··· 22 22 23 23 const isHash = (value) => typeof value === 'string' && value.length === 44 24 24 25 + const getOpenedFromQuery = async (hash) => { 26 + if (!hash) { return null } 27 + const query = await apds.query(hash) 28 + if (Array.isArray(query) && query[0] && query[0].opened) { 29 + return query[0].opened 30 + } 31 + if (query && query.opened) { 32 + return query.opened 33 + } 34 + return null 35 + } 36 + 25 37 const expandHomeLog = async (log) => { 26 38 if (!Array.isArray(log) || !log.length) { return log || [] } 27 39 const entries = [...log] ··· 36 48 queueSend(cursor) 37 49 break 38 50 } 39 - const opened = await apds.open(sig) 51 + const opened = await getOpenedFromQuery(cursor) 40 52 if (!opened || opened.length < 14) { break } 41 53 const contentHash = opened.substring(13) 42 54 const content = await apds.get(contentHash) ··· 48 60 const previous = yaml?.previous 49 61 if (!isHash(previous) || seen.has(previous)) { break } 50 62 queueSend(previous) 51 - const prevSig = await apds.get(previous) 52 - const prevOpened = prevSig ? await apds.open(prevSig) : null 63 + const prevOpened = await getOpenedFromQuery(previous) 53 64 const ts = parseOpenedTimestamp(prevOpened) 54 65 entries.push({ hash: previous, opened: prevOpened, ts }) 55 66 seen.add(previous) ··· 134 145 } 135 146 const check = await document.getElementById(hash) 136 147 if (!check) { 137 - const ts = opened ? Number.parseInt(opened.substring(0, 13), 10) : 0 148 + let ts = 0 149 + if (opened) { 150 + ts = Number.parseInt(opened.substring(0, 13), 10) 151 + if (Number.isNaN(ts)) { ts = 0 } 152 + } 153 + if (!ts) { ts = Date.now() } 138 154 const div = render.insertByTimestamp(scroller, hash, ts) 139 155 if (!div) { return } 140 - await render.blob(src) 156 + if (opened) { div.dataset.opened = opened } 157 + await render.blob(src, { hash, opened }) 141 158 } 142 159 } 143 160 setTimeout(() => {