anproto personal data server

rebrand bog5 server as APDS

+3 -44
README.md
··· 1 - # Bog5 Protocol 2 - 3 - The 5th Bogbook protocol implementation. 4 - 5 - Try it at https://bog5.deno.dev/ 6 - 7 - Please note the above link is not networked. For a fuller and networked version be sure to check out https://wiredove.net/ 8 - 9 - ### The Protocol 10 - 11 - Bogbook has been thru a few iterations. For the fans, thanks for sticking with me. For the people who want standards, I solicit your feedback. And now, the protocol... 12 - 13 - We send around sha256 hashes since we're using content-addressable storage for everything. First we request the hash that finds us a **protocol message**: 14 - 15 - ``` 16 - <ed25519 pubkey><ed25519 sig> 17 - ``` 1 + # APDS 18 2 19 - This opens to: 3 + A personal data server (pds) for ANProto https://anproto.com/ 20 4 21 - ``` 22 - <unix timestamp><sha256 hash> 23 - ``` 24 - 25 - The timestamp is required since this is a social network. The sha256 hashlinks either locally or over the gossip network to a **content message**, which is Yaml file that contains everything else we need to make a post render while not duplicating too much of what we might have already such as avatar photos. 26 - 27 - Everything in the **protocol message** is we need to sort and authenticate a feed. Everything in the **content message** is what we need to render a message. 28 - 29 - ``` 30 - Text content 31 - ``` 32 - 33 - 34 - A full post might look like this: 35 - 36 - ``` 37 - --- 38 - previous: sha256 hash previous protocol message 39 - name: Ev 40 - image: sha256 of image blob 41 - edit: sha256 hash of edited protocol message 42 - --- 43 - Content 44 - ``` 45 - 46 - This means you could in theory edit your name and/or image on a post! But we also want a record of what your name and image was on the original post, since it is a security issue with some actors on decentralized social networks if you allow publishers to change their name over their entire feed. Or, for example, in a hack it would be harder to rewrite history. 5 + Try it at https://pds.anproto.com/ 47 6 48 7 --- 49 8 MIT
+50 -64
bogbot.js
··· 1 - import nacl from './lib/nacl-fast-es.js' 2 - import { decode, encode } from './lib/base64.js' 1 + import { decode } from './lib/base64.js' 3 2 import { cachekv } from './lib/cachekv.js' 4 3 import { human } from './lib/human.js' 5 4 import { vb } from './lib/vb.js' 5 + import { an } from 'https://esm.sh/gh/evbogue/anproto@f7cd761/an.js' 6 6 7 7 let db 8 8 let hashLog = [] ··· 10 10 let newMessages = false 11 11 let sort = true 12 12 13 - export const bogbot = {} 13 + export const apds = {} 14 14 15 - bogbot.start = async (appId) => { 15 + apds.start = async (appId) => { 16 16 db = await cachekv(appId) 17 17 18 18 setInterval(async () => { ··· 40 40 try { 41 41 const obj = { 42 42 hash, 43 - sig: await bogbot.get(hash) 43 + sig: await apds.get(hash) 44 44 } 45 45 obj.author = obj.sig.substring(0, 44) 46 - obj.opened = await bogbot.open(obj.sig) 47 - obj.text = await bogbot.get(obj.opened.substring(13)) 46 + obj.opened = await apds.open(obj.sig) 47 + obj.text = await apds.get(obj.opened.substring(13)) 48 48 obj.ts = obj.opened.substring(0, 13) 49 49 newArray.push(obj) 50 50 } catch (err) { console.log(err) } ··· 65 65 }, 20000) 66 66 } 67 67 68 - bogbot.generate = async () => { 69 - const genkey = nacl.sign.keyPair() 70 - const keygen = encode(genkey.publicKey) + encode(genkey.secretKey) 71 - return keygen 68 + apds.generate = async () => { 69 + const genkey = await an.gen() 70 + return genkey 72 71 } 73 72 74 - bogbot.keypair = async () => { 73 + apds.keypair = async () => { 75 74 const keypair = await db.get('keypair') 76 75 if (keypair) { 77 76 return keypair 78 77 } 79 78 } 80 79 81 - bogbot.pubkey = async () => { 82 - const keypair = await bogbot.keypair() 80 + apds.pubkey = async () => { 81 + const keypair = await apds.keypair() 82 + console.log(keypair) 83 83 if (keypair) { 84 84 return keypair.substring(0, 44) 85 85 } 86 86 } 87 87 88 - bogbot.privkey = async () => { 89 - const keypair = await bogbot.keypair() 88 + apds.privkey = async () => { 89 + const keypair = await apds.keypair() 90 90 if (keypair) { 91 91 return keypair.substring(44) 92 92 } 93 93 } 94 94 95 - bogbot.deletekey = async () => { 95 + apds.deletekey = async () => { 96 96 db.rm('keypair') 97 97 } 98 98 99 - bogbot.clear = async () => { 99 + apds.clear = async () => { 100 100 db.clear() 101 101 } 102 102 103 - bogbot.hash = async (data) => { 104 - return encode( 105 - Array.from( 106 - new Uint8Array( 107 - await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data)) 108 - ) 109 - ) 110 - ) 111 - } 103 + apds.hash = async (data) => { return await an.hash(data) } 112 104 113 - bogbot.sign = async (data) => { 114 - const timestamp = Date.now() 115 - 116 - const hash = await bogbot.make(data) 105 + apds.sign = async (data) => { 106 + const hash = await apds.make(data) 107 + const sig = await an.sign(hash, await apds.keypair()) 108 + console.log(sig) 109 + await apds.add(sig) 110 + const protocolMsg = await apds.make(sig) 117 111 118 - const sig = encode(nacl.sign(new TextEncoder().encode(timestamp + hash), decode(await bogbot.privkey()))) 119 - await bogbot.add(await bogbot.pubkey() + sig) 120 - const protocolMsg = await bogbot.make(await bogbot.pubkey() + sig) 121 112 db.put('previous', protocolMsg) 122 113 return protocolMsg 123 114 } 124 115 125 - bogbot.open = async (msg) => { 116 + apds.open = async (msg) => { 126 117 try { 127 - const pubkey = msg.substring(0, 44) 128 - const sig = msg.substring(44) 129 - 130 - const opened = new TextDecoder().decode(nacl.sign.open(decode(sig), decode(pubkey))) 131 - 132 - return opened 118 + return await an.open(msg) 133 119 } catch (err) { 134 120 //console.log('Not a valid Bog5 protocol message') 135 121 } ··· 137 123 138 124 import { yaml } from './lib/yaml.js' 139 125 140 - bogbot.parseYaml = async (doc) => { 126 + apds.parseYaml = async (doc) => { 141 127 return await yaml.parse(doc) 142 128 } 143 129 144 - bogbot.createYaml = async (obj, content) => { 130 + apds.createYaml = async (obj, content) => { 145 131 return await yaml.create(obj, content) 146 132 } 147 133 148 - bogbot.compose = async (content, prev) => { 134 + apds.compose = async (content, prev) => { 149 135 let obj = {} 150 136 if (prev) { obj = prev } 151 137 ··· 158 144 if (previous) { obj.previous = previous} 159 145 160 146 if (Object.keys(obj).length > 0) { 161 - const yaml = await bogbot.createYaml(obj, content) 162 - return await bogbot.sign(yaml) 147 + const yaml = await apds.createYaml(obj, content) 148 + return await apds.sign(yaml) 163 149 } else { 164 - return await bogbot.sign(content) 150 + return await apds.sign(content) 165 151 } 166 152 } 167 153 168 - bogbot.make = async (data) => { 169 - const hash = await bogbot.hash(data) 154 + apds.make = async (data) => { 155 + const hash = await apds.hash(data) 170 156 171 157 await db.put(hash, data) 172 158 173 159 return hash 174 160 } 175 161 176 - bogbot.get = async (hash) => { 162 + apds.get = async (hash) => { 177 163 const blob = await db.get(hash) 178 164 179 165 return blob 180 166 } 181 167 182 - bogbot.put = async (key, value) => { 168 + apds.put = async (key, value) => { 183 169 await db.put(key, value) 184 170 } 185 171 186 - bogbot.rm = async (key) => { 172 + apds.rm = async (key) => { 187 173 await db.rm(key) 188 174 } 189 175 190 - bogbot.add = async (msg) => { 191 - const opened = await bogbot.open(msg) 176 + apds.add = async (msg) => { 177 + const opened = await apds.open(msg) 192 178 if (opened) { 193 - const hash = await bogbot.make(msg) 179 + const hash = await apds.make(msg) 194 180 if (!hashLog.includes(hash)) { 195 181 hashLog.push(hash) 196 182 const obj = { ··· 199 185 } 200 186 obj.author = obj.sig.substring(0, 44) 201 187 obj.opened = opened 202 - obj.text = await bogbot.get(obj.opened.substring(13)) 188 + obj.text = await apds.get(obj.opened.substring(13)) 203 189 obj.ts = obj.opened.substring(0, 13) 204 190 openedLog.push(obj) 205 191 newMessages = true ··· 208 194 } 209 195 } 210 196 211 - bogbot.getHashLog = async () => { return hashLog } 197 + apds.getHashLog = async () => { return hashLog } 212 198 213 - bogbot.getOpenedLog = async () => { return openedLog } 199 + apds.getOpenedLog = async () => { return openedLog } 214 200 215 - bogbot.query = async (query) => { 201 + apds.query = async (query) => { 216 202 if (openedLog[0] && !query) { return openedLog } 217 203 if (openedLog[0] && query.startsWith('?')) { 218 204 const search = query.substring(1).replace(/%20/g, ' ').toUpperCase() ··· 224 210 } 225 211 } 226 212 227 - bogbot.getPubkeys = async () => { 228 - const arr = await bogbot.query() 213 + apds.getPubkeys = async () => { 214 + const arr = await apds.query() 229 215 const newSet = new Set() 230 216 for (const msg of arr) { 231 217 newSet.add(msg.author) ··· 234 220 return newArr 235 221 } 236 222 237 - bogbot.getLatest = async (pubkey) => { 223 + apds.getLatest = async (pubkey) => { 238 224 const q = openedLog.filter(msg => msg.author === pubkey) 239 225 return q[q.length -1] 240 226 } 241 227 242 - bogbot.human = async (ts) => { 228 + apds.human = async (ts) => { 243 229 return await human(new Date(parseInt(ts))) 244 230 } 245 231 246 - bogbot.visual = async (pubkey) => { 232 + apds.visual = async (pubkey) => { 247 233 return vb(decode(pubkey), 256) 248 234 } 249 235
+2 -2
composer.js
··· 1 - import { bogbot } from './bogbot.js' 1 + import { apds } from './apds.js' 2 2 import { render } from './render.js' 3 3 4 4 export const composer = async () => { ··· 14 14 b.textContent = 'Sign' 15 15 16 16 b.onclick = async () => { 17 - const published = await bogbot.compose(ta.value) 17 + const published = await apds.compose(ta.value) 18 18 ta.value = '' 19 19 const scroller = document.getElementById('scroller') 20 20 await render.hash(published, scroller)
+7 -6
example.js
··· 1 - import { bogbot } from './bogbot.js' 1 + import { apds } from './apds.js' 2 2 import { profile } from './profile.js' 3 3 import { composer } from './composer.js' 4 4 import { render } from './render.js' 5 5 6 - await bogbot.start('bog5example') 6 + await apds.start('apds1') 7 7 8 - if (!await bogbot.pubkey()) { 9 - const keypair = await bogbot.generate() 10 - await bogbot.put('keypair', keypair) 8 + if (!await apds.pubkey()) { 9 + const keypair = await apds.generate() 10 + console.log(keypair) 11 + await apds.put('keypair', keypair) 11 12 } 12 13 13 14 document.body.appendChild(await profile()) ··· 18 19 19 20 document.body.appendChild(scroller) 20 21 21 - const log = await bogbot.query() 22 + const log = await apds.query() 22 23 23 24 log.forEach(async (obj) => { 24 25 await render.hash(obj.hash, scroller)
+11 -11
profile.js
··· 1 1 import { h } from './lib/h.js' 2 - import { bogbot } from './bogbot.js' 2 + import { apds } from './apds.js' 3 3 4 4 export const profile = async () => { 5 5 const div = h('div') 6 6 7 - const avatarImg = await bogbot.visual(await bogbot.pubkey()) 7 + const avatarImg = await apds.visual(await apds.pubkey()) 8 8 9 - const existingImage = await bogbot.get('image') 9 + const existingImage = await apds.get('image') 10 10 11 - if (existingImage) { avatarImg.src = await bogbot.get(existingImage)} 11 + if (existingImage) { avatarImg.src = await apds.get(existingImage)} 12 12 13 13 avatarImg.style = 'height: 30px; width: 30px; float: left; margin-right: 5px; object-fit: cover;' 14 14 ··· 46 46 ctx.drawImage(img, 0, 0, width, height, 0, 0, cropWidth, cropHeight) 47 47 const croppedImage = canvas.toDataURL() 48 48 avatarImg.src = croppedImage 49 - const hash = await bogbot.make(croppedImage) 50 - await bogbot.put('image', hash) 49 + const hash = await apds.make(croppedImage) 50 + await apds.put('image', hash) 51 51 } else { 52 52 const croppedImage = canvas.toDataURL() 53 53 avatarImg.src = img.src 54 - const hash = await bogbot.make(img.src) 55 - await bogbot.put('image', hash) 54 + const hash = await apds.make(img.src) 55 + await apds.put('image', hash) 56 56 } 57 57 } 58 58 img.src = e.target.result ··· 65 65 66 66 div.appendChild(avatarImg) 67 67 68 - div.appendChild(h('div', [await bogbot.pubkey()])) 68 + div.appendChild(h('div', [await apds.pubkey()])) 69 69 70 - const name = await bogbot.get('name') 70 + const name = await apds.get('name') 71 71 72 72 const namer = h('input', { 73 73 placeholder: name || 'Name yourself' ··· 79 79 h('button', {onclick: async () => { 80 80 if (namer.value) { 81 81 namer.placeholder = namer.value 82 - await bogbot.put('name', namer.value) 82 + await apds.put('name', namer.value) 83 83 namer.value = '' 84 84 } 85 85 }}, ['Save'])
+10 -10
render.js
··· 1 - import { bogbot } from './bogbot.js' 1 + import { apds } from './apds.js' 2 2 import { h } from './lib/h.js' 3 3 4 4 export const render = {} 5 5 6 6 render.blob = async (blob) => { 7 - const hash = await bogbot.hash(blob) 7 + const hash = await apds.hash(blob) 8 8 9 9 const div = await document.getElementById(hash) 10 10 11 11 try { 12 - const opened = await bogbot.open(blob) 13 - const ts = h('span', [await bogbot.human(opened.substring(0, 13))]) 12 + const opened = await apds.open(blob) 13 + const ts = h('span', [await apds.human(opened.substring(0, 13))]) 14 14 setInterval(async () => { 15 - ts.textContent = await bogbot.human(opened.substring(0, 13)) 15 + ts.textContent = await apds.human(opened.substring(0, 13)) 16 16 }, 1000) 17 17 if (div) { 18 - const img = await bogbot.visual(blob.substring(0, 44)) 18 + const img = await apds.visual(blob.substring(0, 44)) 19 19 img.id = 'image' 20 20 img.style = 'width: 30px; height: 30px; float: left; margin-right: 5px; object-fit: cover;' 21 21 div.appendChild(img) 22 22 div.appendChild(h('a', {href: '#' + blob.substring(0, 44), id: 'name'}, [blob.substring(0, 10)])) 23 23 div.appendChild(h('a', {href: '#' + hash, style: 'float: right;'}, [ts])) 24 24 div.appendChild(h('div', {id: opened.substring(13)})) 25 - const content = await bogbot.get(opened.substring(13)) 25 + const content = await apds.get(opened.substring(13)) 26 26 if (content) { 27 27 await render.blob(content) 28 28 } ··· 31 31 } 32 32 } catch (err) { 33 33 console.log('Not a valid protocol message') 34 - const yaml = await bogbot.parseYaml(blob) 34 + const yaml = await apds.parseYaml(blob) 35 35 if (div) { 36 36 div.textContent = yaml.body 37 37 div.parentNode.childNodes.forEach(async (node) => { ··· 39 39 node.textContent = yaml.name 40 40 } 41 41 if (yaml.image && node.id === 'image') { 42 - const image = await bogbot.get(yaml.image) 42 + const image = await apds.get(yaml.image) 43 43 node.src = image 44 44 } 45 45 }) ··· 53 53 54 54 scroller.insertBefore(div, scroller.firstChild) 55 55 56 - const sig = await bogbot.get(hash) 56 + const sig = await apds.get(hash) 57 57 58 58 if (sig) { 59 59 await render.blob(sig)