Browse Source

major refactor -- new look and messages are stored as signatures - 1.3.0

english
Ev Bogue 3 years ago
parent
commit
bb78d6fb69
  1. 226
      app.js
  2. 128
      bog.js
  3. 109
      css/style.css
  4. 19
      index.html
  5. 230
      lib.js
  6. 64
      package-lock.json
  7. 4
      package.json
  8. 143
      render.js
  9. 56
      server.js
  10. 59
      views.js
  11. 67
      welcome.js

226
app.js

@ -1,176 +1,94 @@
var screen = h('div', {id: 'screen'})
document.body.appendChild(screen)
function keyPage (keys) {
var scroller = document.getElementById('scroller')
var message = h('div', {classList: 'message'})
message.appendChild(h('p', {innerHTML: marked('This is your ed25519 public/private keypair. It was generated using [Tweetnacl.js](https://tweetnacl.js.org/#/). Your public key is your identity when using [Bogbook](http://bogbook.com/), save your key in a safe place so that you can continue to use the same identity.')}))
// print stringified keypair
message.appendChild(h('pre', {style: 'width: 80%'}, [h('code', [JSON.stringify(keys)])]))
// delete key button
message.appendChild(h('button', {
onclick: function () {
localforage.removeItem('id', function () {
location.hash = ''
location.reload()
})
}
}, ['Delete Key']))
var textarea = h('textarea', {placeholder: 'Import your existing ed25519 keypair'})
message.appendChild(textarea)
message.appendChild(h('button', {
onclick: function () {
if (textarea.value) {
localforage.setItem('id', JSON.parse(textarea.value))
location.reload()
}
}
}, ['Import Key']))
scroller.appendChild(message)
}
function profilePage (src, keys) {
var scroller = document.getElementById('scroller')
function composer (keys, reply) {
var messageDiv = h('div')
var message = h('div', {classList: 'message'})
var identify = h('input', {placeholder: 'Identify ' + src.substring(0, 10) + '...'})
message.appendChild(h('div', [
identify,
h('button', {onclick: function () {
if (identify.value) {
var content = {
author: keys.publicKey,
type: 'name',
naming: src,
name: identify.value,
timestamp: Date.now()
}
identify.value = ''
publish(content, keys)
localforage.setItem('id', keys, function (err, published) {
if (published) {
location.reload()
var textarea = h('textarea', {placeholder: 'Write a new bog post...'})
var publisher = h('div', [
textarea,
h('button', {
onclick: function () {
if (textarea.value) {
var content = {
type: 'post',
text: textarea.value,
timestamp: Date.now()
}
})
}
}}, ['Identify'])
]))
scroller.appendChild(message)
requestFeed(src, 'ws://localhost:8080/', keys.publicKey)
localforage.getItem(src, function (err, log) {
if (log) {
for (var i=0; i < log.length; i++) {
var post = log[i]
scroller.appendChild(renderMessage(post))
if (reply) {
content.reply = reply.key
}
publish(content, keys).then(post => {
open(post).then(msg => {
textarea.value = ''
if (reply) {
messageDiv.removeChild(messageDiv.firstChild)
if (messageDiv.firstChild) {
messageDiv.insertBefore(h('div', {classList: 'submessage'}, [render(msg, keys)]), messageDiv.childNodes[1])
} else {
messageDiv.appendChild(h('div', {classList: 'submessage'}, [render(msg, keys)]))
}
} else {
if (messageDiv.firstChild) {
messageDiv.insertBefore(render(msg, keys), messageDiv.childNodes[1])
} else {
messageDiv.appendChild(render(msg, keys))
}
}
})
})
}
}
}
})
}
}, ['Publish'])
])
function threadPage (src, keys) {
var scroller = document.getElementById('scroller')
localforage.getItem('log', function (err, log) {
for (var i = log.length - 1; i >= 0; --i) {
if (log[i].key === src) {
var post = log[i]
scroller.appendChild(renderMessage(post))
}
}
})
message.appendChild(publisher)
messageDiv.appendChild(message)
return messageDiv
}
function publicPage (keys) {
compose(keys)
function route (keys) {
src = window.location.hash.substring(1)
localforage.getItem('log', function (err, log) {
if (log) {
for (var i=0; i < log.length; i++) {
var post = log[i]
scroller.appendChild(renderMessage(post))
}
var newLog = log.sort(function (a, b) {
return b.content.timestamp - a.content.timestamp
})
if (newLog) {
localforage.setItem('log', log)
}
}
})
}
var scroller = h('div', {id: 'scroller'})
var screen = document.getElementById('screen')
screen.appendChild(scroller)
function route () {
localforage.getItem('id', function (err, keys) {
src = window.location.hash.substring(1)
var scroller = h('div', {id: 'scroller'})
var screen = document.getElementById('screen')
screen.appendChild(scroller)
if (src === 'key') {
keyPage(keys)
} else if (src[0] === '@') {
profilePage(src, keys)
} else if (src[0] === '%') {
threadPage(src, keys)
} else {
publicPage(keys)
}
})
if (src === 'key') {
keyPage(keys)
} else if (src[0] === '@') {
profilePage(src, keys)
} else if (src[0] === '%') {
threadPage(src, keys)
} else {
publicPage(keys)
}
}
localforage.getItem('id', function (err, keys) {
if (keys) {
keys().then(key => {
var navbar = h('div', {classList: 'navbar'}, [
h('div', {classList: 'internal'}, [
h('li', [h('a', {href: '/#'}, ['Home'])]),
h('li', [h('a', {href: '#' + keys.publicKey}, [getName(keys.publicKey)])]),
h('li', [h('a', {href: '/#key'}, ['Key'])])
])
var navbar = h('div', {classList: 'navbar'}, [
h('div', {classList: 'internal'}, [
h('li', [h('a', {href: '#'}, ['Home'])]),
h('li', [h('a', {href: '#' + key.publicKey}, [getName(key.publicKey)])]),
h('li', [h('a', {href: '#key'}, ['Key'])])
])
])
document.body.appendChild(navbar)
document.body.appendChild(navbar)
route()
} else {
var genkey = nacl.sign.keyPair()
if (genkey) {
var keys = {
publicKey: '@' + nacl.util.encodeBase64(genkey.publicKey),
privateKey: nacl.util.encodeBase64(genkey.secretKey)
}
if (keys.publicKey.includes('/')) {
console.log('TRYING AGAIN')
setTimeout(function () {
location.reload()
}, 10)
} else {
welcomeScreen(keys)
}
}
}
route(key)
})
window.onhashchange = function () {
var oldscreen = document.getElementById('screen')
var newscreen = h('div', {id: 'screen'})
oldscreen.parentNode.replaceChild(newscreen, oldscreen)
route()
keys().then(key => {
var oldscreen = document.getElementById('screen')
var newscreen = h('div', {id: 'screen'})
oldscreen.parentNode.replaceChild(newscreen, oldscreen)
route(key)
})
}

128
bog.js

@ -0,0 +1,128 @@
// bog.open -- opens a signature and returns content if you pass a signature and a public key
// EX: open(msg).then(content => { console.log(content) })
async function open (msg) {
var pubkey = nacl.util.decodeBase64(msg.author.substring(1))
var sig = nacl.util.decodeBase64(msg.signature)
var opened = await JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
opened.key = msg.key
return opened
}
// bog.keys -- gets your public/private keypair, generates one if there is none
// EX: keys().then(key => { console.log(key)})
async function keys (key) {
var keypair = await localforage.getItem('id')
if (keypair != null) {
return keypair
} else {
var genkey = nacl.sign.keyPair()
var keypair = {
publicKey: '@' + nacl.util.encodeBase64(genkey.publicKey),
privateKey: nacl.util.encodeBase64(genkey.secretKey)
}
localforage.setItem('id', keypair)
return keypair
}
}
// bog.get -- iterates over log and returns a post.
// EX: get('%x5T7KZ5haR2F59ynUuCggwEdFXlLHEtFoBQIyKYppZYerq9oMoIqH76YzXQpw2DnYiM0ugEjePXv61g3E4l/Gw==').then(msg => { console.log(msg)})
async function get (key) {
var log = await localforage.getItem('log')
if (log != null) {
for (var i = log.length - 1; i >= 0; --i) {
if (log[i].key === key) {
return log[i]
}
}
}
}
// bog.getName -- iterates over a feed and returns a person's name
function getName (id) {
var name = h('span')
name.textContent = id.substring(0, 10) + '...'
return name
}
// bog.log (feed) -- returns a specific feed if a parameter is passed, if not returns the entire log
// EX: bog().then(log => { console.log(log)})
// EX: bog('@ExE3QXmBhYQlGVA3WM2BD851turNzwhruWbIpMd7rbQ=').then(log => { console.log(log)})
async function bog (feed) {
if (feed) {
var log = await localforage.getItem(feed)
return log
} else {
var log = await localforage.getItem('log')
return log
}
}
// bog.publish -- publishes a new bog post and updates the feeds
// EX: publish({type: 'post', timestamp: Date.now(), text: 'Hello World'}).then(msg => { console.log(msg)})
async function publish (post, keys) {
post.author = keys.publicKey
var message = { author: keys.publicKey }
var feed = await localforage.getItem(keys.publicKey)
if (feed) {
var firstMsg = await open(feed[0])
post.seq = ++firstMsg.seq
message.key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(post)))),
message.signature = nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(post)), nacl.util.decodeBase64(keys.privateKey)))
localforage.getItem('log').then(log => {
if (log) {
log.unshift(message)
localforage.setItem('log', log)
} else {
var feed = [message]
localforage.setItem('log', feed)
}
})
feed.unshift(message)
localforage.setItem(keys.publicKey, feed)
return message
} else {
post.seq = 0
message.key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(post)))),
message.signature = nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(post)), nacl.util.decodeBase64(keys.privateKey)))
localforage.getItem('log').then(log => {
if (log) {
log.unshift(message)
localforage.setItem('log', log)
} else {
var feed = [message]
localforage.setItem('log', feed)
}
})
var feed = [message]
localforage.setItem(keys.publicKey, feed)
return message
}
}

109
css/style.css

@ -1,7 +1,7 @@
body {
font-family: 'Source Sans Pro';
background: #222;
color: #f5f5f5;
background: #f5f5f5;
color: #5a5a5a;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
@ -49,12 +49,23 @@ hr {
.right { float: right;}
.message {
background: #333;
border: 1px solid #ddd;
background: white;
margin-top: .5em;
padding: .3em .5em;
border-radius: 5px;
}
.message, .message > *, .navbar, .navbar > * {
animation: fadein .5s;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.submessage {
margin-left: 2em;
}
@ -73,7 +84,7 @@ img.small {
}
a {
color: cyan;
color: #0088cc;
text-decoration: none;
}
@ -82,7 +93,7 @@ a:hover {
}
pre {
color: violet;
color: #dd1144;
width: 100%;
display: block;
}
@ -105,13 +116,17 @@ code, pre {
textarea, input {
font-family: 'Source Sans Pro';
font-size: 1em;
background: #222;
background: #f5f5f5;
padding: .5em;
color: #f5f5f5;
color: #5a5a5a;
border: none;
border-radius: 5px;
}
textarea:active, textarea:focus {
background: white;
}
textarea {
width: 100%;
}
@ -170,28 +185,82 @@ textarea {
button {
display: inline-block;
padding: .25em .5em;
margin: .15em;
font-size: 1em;
line-height: 1.2em;
color: #d5d5d5;
*display: inline;
padding: 4px 12px;
margin-top: .5em;
margin-bottom: 0;
*margin-left: .3em;
font-size: 14px;
line-height: 20px;
color: #333333;
text-align: center;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
vertical-align: middle;
cursor: pointer;
background-color: #222;
border: 1px solid #222;
border-radius: 4px;
background-color: #f5f5f5;
*background-color: #e6e6e6;
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
background-repeat: repeat-x;
border: 1px solid #cccccc;
*border: 0;
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
border-bottom-color: #b3b3b3;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
*zoom: 1;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
}
button:hover,
button:focus,
button:active,
button[disabled] {
color: #333333;
background-color: #e6e6e6;
*background-color: #d9d9d9;
}
button:active {
color: white;
background-color: black;
background-color: #cccccc \9;
}
button:first-child {
*margin-left: 0;
}
button.active {
background-color: #111;
button:hover,
button:focus {
color: #333333;
text-decoration: none;
background-position: 0 -15px;
-webkit-transition: background-position 0.1s linear;
-moz-transition: background-position 0.1s linear;
-o-transition: background-position 0.1s linear;
transition: background-position 0.1s linear;
}
button:focus {
outline: thin dotted #333;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
button:active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
}

19
index.html

@ -2,18 +2,19 @@
<head>
<title>Bogbook</title>
<meta name='viewport' content='width=device-width initial-scale=1' />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel='stylesheet' href='./css/source-sans-pro.min.css' />
<link rel='stylesheet' href='./css/style.css' />
<script src="./lib/nacl.min.js"></script>
<script src="./lib/nacl-util.min.js"></script>
<script src="./lib/localforage.min.js"></script>
<script src="./lib/marked.min.js"></script>
<script src="./lib/misc.js"></script>
<script async src="bog.js"></script>
<script async src="render.js"></script>
<script async src="views.js"></script>
<script async src="app.js"></script>
</head>
<body>
<script src="./lib/nacl.min.js"></script>
<script src="./lib/nacl-util.min.js"></script>
<script src="./lib/localforage.min.js"></script>
<script src="./lib/marked.min.js"></script>
<script src="./lib/misc.js"></script>
<script src="lib.js"></script>
<script src="welcome.js"></script>
<script src="render.js"></script>
<script src="app.js"></script>
</body>
</html>

230
lib.js

@ -1,230 +0,0 @@
/*function requestFeed (src, server, requester) {
console.log(src)
console.log(server)
var ws = new WebSocket(server + src)
localforage.getItem(src, function (err, log) {
if (log) {
console.log(log)
var post = log[0]
var pubkey = nacl.util.decodeBase64(src.substring(1))
var sig = nacl.util.decodeBase64(post.signature)
post.content = JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
var seq = post.content.sequence
ws.onopen = function () {
var req = {
feed: src,
seq,
requester
}
console.log(req)
ws.send(JSON.stringify(req))
}
ws.onmessage = function (message) {
var res = JSON.parse(message.data)
if (res.seq == null) {
console.log('SENDING ENTIRE LOG')
var send = {
feed: src,
log,
requester
}
ws.send(JSON.stringify(send))
}
if (seq > res.req) {
console.log('SENDING')
console.log(log)
}
}
} else {
ws.onopen = function () {
var seq = null
}
ws.onmessage = function (message) {
console.log(message.data)
}
}
})
}*/
// publish new messages to your log
function publish (toPublish, keys) {
localforage.getItem(keys.publicKey, function (err, log) {
if (log) {
var lastPost = log[0]
var pubkey = nacl.util.decodeBase64(keys.publicKey.substring(1))
var sig = nacl.util.decodeBase64(lastPost.signature)
var opened = JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
var seq = opened.sequence
toPublish.sequence = ++seq
toPublish.previous = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(log[0]))))
var author = keys.publicKey
var key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(toPublish))))
var signature = nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(toPublish)), nacl.util.decodeBase64(keys.privateKey)))
var toPost = {
author,
key,
signature
}
console.log(toPost)
// update the log
updateLog(keys.publicKey, toPost)
/*var scroller = document.getElementById('scroller')
if (scroller.firstChild) {
scroller.insertBefore(renderMessage(toPost), scroller.childNodes[1])
} else {
scroller.appendChild(renderMessage(toPost))
}*/
} else {
toPublish.sequence = 0
var toPost = {
author: keys.publicKey,
key: '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(toPublish)))),
signature: nacl.util.encodeBase64(nacl.sign(nacl.util.decodeUTF8(JSON.stringify(toPublish)), nacl.util.decodeBase64(keys.privateKey)))
}
updateLog(keys.publicKey, toPost)
/*var scroller = document.getElementById('scroller')
if (scroller.firstChild) {
scroller.insertBefore(renderMessage(toPost), scroller.childNodes[1])
} else {
scroller.appendChild(renderMessage(toPost))
}*/
}
})
}
function compose (keys) {
var message = h('div', {classList: 'message'})
var scroller = document.getElementById('scroller')
scroller.insertBefore(message, scroller.firstChild)
var textarea = h('textarea', {placeholder: 'Write a new bog post'})
message.appendChild(textarea)
var composer = h('div', [
h('button', {
onclick: function () {
if (textarea.value) {
var toPublish = {
author: keys.publicKey,
type: 'post',
text: textarea.value,
timestamp: Date.now()
}
textarea.value = ''
publish(toPublish, keys)
}
}
}, ['Publish'])
])
message.appendChild(composer)
}
// update your log in the browser
function updateLog (feed, post) {
localforage.getItem(feed, function (err, log) {
if (log) {
log.unshift(post)
localforage.setItem(feed, log, function () {
console.log('FEED UPDATED')
})
} else {
log = []
log.unshift(post)
localforage.setItem(feed, log, function () {
console.log('FEED UPDATED')
})
}
})
localforage.getItem('log', function (err, log) {
if (log) {
log.unshift(post)
localforage.setItem('log', log, function () {
console.log('LOG UPDATED')
location.reload()
})
} else {
log = []
log.unshift(post)
localforage.setItem('log', log, function () {
console.log('LOG UPDATED')
location.reload()
})
}
})
}
function getName (id) {
var name = h('span')
name.textContent = id.substring(0, 10) + '...'
localforage.getItem(id, function (err, log) {
if (log) {
for (var i=0; i < log.length; i++) {
var post = log[i]
var pubkey = nacl.util.decodeBase64(post.author.substring(1))
var sig = nacl.util.decodeBase64(post.signature)
post.content = JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
//if (post.content) {
if (post.content.type == 'name') {
name.textContent = '@' + post.content.name
}
//}
}
}
})
return name
}
function getHeader (post, mini) {
var inner
if (mini) {
var inner = mini
}
var head = h('span', [
h('a', {href: '#' + post.key}, [
h('p', {classList: 'right'}, [human(new Date(post.content.timestamp))]),
]),
h('p', [
h('a', {href: '#' + post.content.author}, [
getName(post.content.author)
]),
inner
])
])
return head
}

64
package-lock.json generated

@ -1,75 +1,21 @@
{
"name": "bogbook",
"version": "1.1.0",
"version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"ecstatic": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.1.tgz",
"integrity": "sha512-/rrctvxZ78HMI/tPIsqdvFKHHscxR3IJuKrZI2ZoUgkt2SiufyLFBmcco+aqQBIu6P1qBsUNG3drAAGLx80vTQ==",
"requires": {
"he": "^1.1.1",
"mime": "^1.6.0",
"minimist": "^1.1.0",
"url-join": "^2.0.5"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
"integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"opn": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz",
"integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==",
"open": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/open/-/open-6.2.0.tgz",
"integrity": "sha512-Vxf6HJkwrqmvh9UAID3MnMYXntbTxKLOSfOnO7LJdzPf3NE3KQYFNV0/Lcz2VAndbRFil58XVCyh8tiX11fiYw==",
"requires": {
"is-wsl": "^1.1.0"
}
},
"tweetnacl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz",
"integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A=="
},
"tweetnacl-util": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.0.tgz",
"integrity": "sha1-RXbBzuXi1j0gf+5S8boCgZSAvHU="
},
"url-join": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
}

4
package.json

@ -1,6 +1,6 @@
{
"name": "bogbook",
"version": "1.2.0",
"version": "1.3.0",
"description": "secure blockchain logging (blogging, without the l) -- bogging",
"main": "server.js",
"scripts": {
@ -11,7 +11,7 @@
"license": "MIT",
"dependencies": {
"ecstatic": "^3.3.1",
"opn": "^6.0.0",
"open": "^6.2.0",
"tweetnacl": "^1.0.1",
"tweetnacl-util": "^0.15.0",
"ws": "^6.2.1"

143
render.js

@ -1,118 +1,53 @@
function renderMessage (post) {
var messageDiv = h('messageDiv', {id: post.key})
var message = h('div', {classList: 'message'})
var pubkey = nacl.util.decodeBase64(post.author.substring(1))
var sig = nacl.util.decodeBase64(post.signature)
post.content = JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
if (post.content.type == 'name') {
var mini = h('span', [
' identified as ',
post.content.name
function getHeader (post, mini) {
var head = h('span', [
h('a', {href: '#' + post.key}, [
h('p', {classList: 'right'}, [human(new Date(post.timestamp))]),
]),
h('p', [
h('a', {href: '#' + post.author}, [
getName(post.author)
]),
mini
])
])
return head
}
message.appendChild(getHeader(post, mini))
messageDiv.appendChild(message)
}
if (post.content.type == 'post') {
function render (msg, keys) {
var messageDiv = h('messageDiv', {id: msg.key})
var message = h('div', {classList: 'message'})
/*
NEED TO UNBOX THREADS
localforage.getItem('log', function (err, log) {
if (log) {
for (var i = log.length - 1; i >= 0; --i) {
if (log[i].content.reply == post.key) {
var nextPost = log[i]
var messageExists = (document.getElementById(nextPost.key) !== null);
if (!messageExists) {
messageDiv.appendChild(h('div', {classList: 'submessage'}, [
renderMessage(nextPost)
]))
}
bog().then(log => {
log.forEach(function (nextPost) {
open(nextPost).then(nextMessage => {
var messageExists = (document.getElementById(nextMessage.key) !== null);
if (nextMessage.reply == msg.key) {
if (!messageExists) {
messageDiv.appendChild(h('div', {classList: 'submessage'}, [render(nextMessage, keys)]))
}
}
}
})*/
var renderer = new marked.Renderer();
renderer.link = function(href, title, text) {
if ((href[0] == '@') || (href[0] == '%')) {
href = '#' + href
}
var link = marked.Renderer.prototype.link.call(this, href, title, text);
return link
}
marked.setOptions({
renderer: renderer
});
})
})
})
if (msg.type == 'post') {
message.appendChild(getHeader(msg))
message.appendChild(getHeader(post))
if (post.content.reply) {
if (msg.reply) {
message.appendChild(h('span', [
're: ',
h('a', {href: '#' + post.content.reply}, [post.content.reply.substring(0, 10) + '...'])
h('a', {href: '#' + msg.reply}, [msg.reply.substring(0, 10) + '...'])
]))
}
message.appendChild(h('div', {innerHTML: marked(post.content.text)}))
message.appendChild(h('span', {id: post.key + 'src', classList: 'right'}, [
h('a', {
onclick: function () {
message.appendChild(h('pre', [JSON.stringify(post)]))
var span = document.getElementById(post.key + 'src')
span.parentNode.removeChild(span)
}
}, ['[src]'])
]))
var gotName = getName(post.content.author)
localforage.getItem('id', function (err, keys) {
var publishButton = h('button', {
onclick: function () {
if (textarea.value) {
var content = {
author: keys.publicKey,
type: 'post',
text: textarea.value,
reply: post.key,
timestamp: Date.now()
}
publish(content, keys)
message.removeChild(textarea)
message.removeChild(publishButton)
}
}
}, ['Publish'])
var textarea = h('textarea', {placeholder: 'Reply to this bog post'}, ['['+ gotName.textContent + '](' + post.content.author + ')'])
var replyButton = h('button', {
classList: 'replyButton:' + post.key,
onclick: function () {
message.removeChild(replyButton)
message.appendChild(textarea)
message.appendChild(publishButton)
}
}, ['Reply'])
message.appendChild(replyButton)
})
messageDiv.appendChild(message)
}
message.appendChild(h('div', [msg.text]))
message.appendChild(h('button', {
onclick: function () {
messageDiv.appendChild(composer(keys, msg))
}
}, ['Reply']))
}
messageDiv.appendChild(message)
return messageDiv
}

56
server.js

@ -2,62 +2,12 @@
var http = require('http')
var serve = require('ecstatic')
var opn = require('opn')
var open = require('open')
http.createServer(
serve({ root: __dirname})
).listen(8089)
opn('http://localhost:8089')
// websocket server (8080)
var WebSocket = require('ws')
var fs = require('fs')
var nacl = require('tweetnacl')
nacl.util = require('tweetnacl-util')
var wserver = new WebSocket.Server({ port: 8080 })
wserver.on('connection', function (ws) {
ws.on('message', function (message) {
var data = JSON.parse(message)
console.log(data)
// initial req/res contains a sequence number
if (data.seq) {
if (fs.existsSync(__dirname + '/bogs/' + data.feed)) {
fs.readFile(__dirname + '/bogs/' + data.feed, 'UTF-8', function (err, data) {
if (data) {
var log = JSON.parse(data)
console.log(log)
}
})
} else {
var res = {
feed: data.feed,
seq: null
}
console.log(res)
ws.send(JSON.stringify(res))
}
}
// if the client has a longer log, it'll send one for the server to save
if (data.log) {
console.log(data)
if (fs.existsSync(__dirname + '/bogs/' + data.feed)) {
var log = JSON.parse(fs.readFileSync(__dirname + '/bogs/' + data.feed))
console.log(log)
} else {
fs.writeFile(__dirname + '/bogs/' + data.feed, JSON.stringify(data.log), function (err, success) {
console.log('saved ' + data.feed + ' sent by ' + data.requester)
})
}
}
})
})
open('http://localhost:8089')
// ws server (8080)

59
views.js

@ -0,0 +1,59 @@
function threadPage (src, keys) {
get(src).then(msg => {
open(msg).then(post => {
scroller.appendChild(render(post, keys))
})
})
}
function profilePage (src, keys) {
bog(src).then(log => {
log.forEach(function (msg) {
open(msg).then(post => {
scroller.appendChild(render(post, keys))
})
})
})
}
function publicPage (keys) {
scroller.appendChild(composer(keys))
bog().then(log => {
log.forEach(function (msg) {
open(msg).then(post => {
scroller.appendChild(render(post, keys))
})
})
})
}
function keyPage (keys) {
var message = h('div', {classList: 'message'})
message.appendChild(h('p', {innerHTML: marked('This is your ed25519 public/private keypair. It was generated using [TweetNaCl.js](https://tweetnacl.js.org/#/). Your public key is your identity when using [Bogbook](http://bogbook.com/), save your key in a safe place so that you can continue to use the same identity.')}))
message.appendChild(h('pre', {style: 'width: 80%'}, [h('code', [JSON.stringify(keys)])]))
message.appendChild(h('button', {
onclick: function () {
localforage.removeItem('id', function () {
location.hash = ''
location.reload()
})
}
}, ['Delete Key']))
var textarea = h('textarea', {placeholder: 'Import your existing ed25519 keypair'})
message.appendChild(textarea)
message.appendChild(h('button', {
onclick: function () {
if (textarea.value) {
localforage.setItem('id', JSON.parse(textarea.value))
location.reload()
}
}
}, ['Import Key']))
scroller.appendChild(message)
}

67
welcome.js

@ -1,67 +0,0 @@
function welcomeScreen (keys) {
var screen = document.getElementById('screen')
var scroller = h('div', {id: 'scroller'})
screen.appendChild(scroller)
var message = h('div', {classList: 'message'})
scroller.appendChild(message)
message.appendChild(h('h1', ['Welcome to Bogbook']))
message.appendChild(h('p', ['Bogbook is a distributed blogging network of signed append-only feeds. We call them "bogs".']))
message.appendChild(h('p', ['Please note: Bogbook is experimental software, not for use in producton environments. Expect bugs and breaking changes. Pull-requests are needed.']))
message.appendChild(h('p', {innerHTML: marked('View the code: [http://github.com/bogbook/bog](http://github.com/bogbook/bog). Questions? [ev@evbogue.com](mailto:ev@evbogue.com).')}))
message.appendChild(h('hr'))
message.appendChild(h('h3', ['Get started']))
message.appendChild(h('p', {innerHTML: marked('This is an ed25519 public/private signing keypair. It was generated using [TweetNaCl.js](https://tweetnacl.js.org/#/)')}))
message.appendChild(h('pre', [JSON.stringify(keys)]))
message.appendChild(h('p', ['Right now, this keypair exists only in memory. When you leave this page, the keypair will vanish forever. If you refresh this page you\'ll receive a new keypair.']))
message.appendChild(h('p', {innerHTML: marked('To save this keypair, identify with a handle below. Once you identify, your public/private keypair will be stored in your browser using [localForage.js](https://localforage.github.io/localForage). Save your keypair somewhere safe to preserve your identity.')}))
message.appendChild(h('hr'))
message.appendChild(h('h3', ['Identify']))
var identify = h('input', {placeholder: 'Your Name'})
message.appendChild(h('div', [
identify,
h('button', {onclick: function () {
if (identify.value) {
var toPublish = {
author: keys.publicKey,
type: 'name',
naming: keys.publicKey,
name: identify.value,
timestamp: Date.now()
}
identify.value = ''
publish(toPublish, keys)
localforage.setItem('id', keys, function (err, published) {
if (published) {
location.hash = ''
location.reload()
}
})
}
}}, ['Identify'])
]))
message.appendChild(h('p', ['When you click [Identify], you will post your first message to your append-only bog, your ed25519 keypair will be saved in your browser, and the page will reload. Don\'t forget to back up your key! and happy bogging.']))
message.appendChild(h('hr'))
message.appendChild(h('h3', ['Already have a key?']))
message.appendChild(h('p', ['Import it here. Make sure to sync your existing feed from a Bogbook \'pub\' before posting a message.']))
var textarea = h('textarea', {placeholder: 'Import your existing ed25519 keypair'})
message.appendChild(textarea)
message.appendChild(h('button', {
onclick: function () {
if (textarea.value) {
localforage.setItem('id', JSON.parse(textarea.value))
location.hash = ''
location.reload()
}
}
}, ['Import Key']))
}
Loading…
Cancel
Save