Browse Source

Merge new contents and translate as much as possible TODO: translate settings.js

master
Joop Kiefte 1 year ago
parent
commit
d5aa1cc524
13 changed files with 1531 additions and 750 deletions
  1. +15
    -52
      app.js
  2. +80
    -53
      bog.js
  3. +6
    -5
      composer.js
  4. +57
    -12
      css/style.css
  5. +175
    -242
      gossip.js
  6. +206
    -27
      identify.js
  7. +2
    -1
      index.html
  8. +366
    -51
      package-lock.json
  9. +3
    -3
      package.json
  10. +157
    -58
      render.js
  11. +156
    -100
      server.js
  12. +102
    -0
      settings.js
  13. +206
    -146
      views.js

+ 15
- 52
app.js View File

@ -8,53 +8,8 @@ function route (keys) {
var screen = document.getElementById('screen')
screen.appendChild(scroller)
function nameCheck (id) {
localforage.getItem('name:' + id).then(name => {
if (!name) {
var identify = h('div', {id: 'identify', classList: 'message'})
scroller.appendChild(identify)
identify.appendChild(h('span', {innerHTML: marked("Saluton [" + keys.publicKey.substring(0, 10) + "...](#"+ keys.publicKey +")! Bonvenon al Interskri.be. Se vi havas ajnan demandon, sentu vin libera demandi ĉe [@LaPingvino](#@gl6HzjWL8SndhDGpN1mtLkf6OXdBQi67vaAyCoCnsCU=).")}))
identify.appendChild(h('span', {innerHTML: marked("Via nuna publika ŝlosilo ankoraŭ ne havas nomon. Vi povas ĉu importi vian ekzistantant identecon en la [ŝlosilo](#key)-paĝo, aŭ identigi vin per la suba kesto. Ne estas ajna devigo identigi vin, sed vi vidos ĉi tiun bonvenigon dum vi ne donis nomon al vi.")}))
var input = h('input', {placeholder: 'Donu nomon por vi'})
identify.appendChild(h('div', [
input,
h('button', {
onclick: function () {
if (input.value) {
content = {
type: 'name',
named: id,
name: input.value
}
publish(content, keys)
setTimeout(function () {
getName(id, keys)
setTimeout(function () {
location.reload()
}, 1000)
}, 1000)
}
}
}, ['Identigi'])
]))
identify.appendChild(h('span', {innerHTML: marked("Sekve, certigu ke via paro de publika kaj privata ŝlosilo de la [ŝlosilo](#key)-paĝo estu konservita en sekura loko por ke vi povu uzadi la saman identecon. Nur vi povas aliri vian privatan ŝlosilon, do nur vi povas restarigi la eblon afiŝi al ĉi tiu identeco. Se vi perdas vian ŝlosilon, vi perdas vian kapablon afiŝi al ĉi tiu identeco por ĉiam.")}))
identify.appendChild(h('span', {innerHTML: marked("Fine, la fontkodo de ĉi tiu paĝo troveblas en [mia persona kod-deponejo](https://git.kiefte.eu/lapingvino/bogbook-esperanto)")}))
}
})
}
nameCheck(keys.publicKey)
if (src === 'key') {
keyPage(keys)
} else if (src === 'pubs') {
pubs()
if (src === 'settings') {
settingsPage(keys)
} else if (src[0] === '@') {
profilePage(src, keys)
} else if (src[0] === '?') {
@ -71,11 +26,19 @@ keys().then(key => {
var navbar = h('div', {classList: 'navbar'}, [
h('div', {classList: 'internal'}, [
h('li', [h('a', {href: '#'}, ['Hejmo'])]),
h('li', [h('a', {href: '#' + key.publicKey}, [getName(key.publicKey, keys)])]),
h('li', [h('a', {href: '#key'}, ['Ŝlosilo'])]),
h('li', [h('a', {href: '#pubs'}, ['Konigejoj'])]),
h('li', {classList: 'right'}, [h('a', {href: 'https://git.kiefte.eu/lapingvino/bogbook-esperanto'}, ['Fontkodo'])]),
h('li', [h('a', {href: '#' + key.publicKey},
[
getImage(key.publicKey, keys),
])
]),
h('li', [h('a', {href: '#' + key.publicKey},
[
getName(key.publicKey, keys)
])
]),
h('li', [h('a', {href: '#'}, ['Ĉiuj'])]),
h('li', [h('a', {href: '#?' + key.publicKey}, ['Mencioj'])]),
h('li', {classList: 'right'}, [h('a', {href: '#settings'}, ['Agordoj'])]),
h('form', { classList: 'search',
onsubmit: function (e) {
window.location.hash = '?' + search.value


+ 80
- 53
bog.js View File

@ -12,7 +12,6 @@ if ((typeof process !== 'undefined') && (process.release.name === 'node')) {
// 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)))
@ -77,11 +76,10 @@ async function unbox (boxed, sender, keys) {
var nonceMsg = nacl.util.decodeBase64(boxed)
var nonce = nonceMsg.slice(0, nacl.box.nonceLength)
var msg = nonceMsg.slice(nacl.box.nonceLength, nonceMsg.length)
var message = nacl.box.open(msg, nonce, ed2curve.convertPublicKey(nacl.util.decodeBase64(sender.substring(1))), ed2curve.convertSecretKey(nacl.util.decodeBase64(keys.privateKey)))
var message = nacl.util.encodeUTF8(nacl.box.open(msg, nonce, ed2curve.convertPublicKey(nacl.util.decodeBase64(sender.substring(1))), ed2curve.convertSecretKey(nacl.util.decodeBase64(keys.privateKey))))
return message
}
// bog.get -- iterates over log and returns a post.
// EX: get('%x5T7KZ5haR2F59ynUuCggwEdFXlLHEtFoBQIyKYppZYerq9oMoIqH76YzXQpw2DnYiM0ugEjePXv61g3E4l/Gw==').then(msg => { console.log(msg)})
@ -96,35 +94,40 @@ async function get (key) {
}
}
async function getTitle (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].text.substring(0, 15) + '…'
}
}
}
}
// bog.getImage
function getImage (id, keys) {
var image = h('img', {classList: 'avatar'})
function getImage (id, keys, classList) {
if (classList) {
var image = h('img', {classList: classList})
} else {
var image = h('img', {classList: 'avatar'})
}
localforage.getItem('image:' + id).then(cache => {
if (cache) {
console.log('GOT IMAGE FROM CACHE: ' + cache)
return image.src = cache
} else {
bog().then(log => {
if (log) {
for (var i = 0; i < log.length; i++) {
if ((log[i].imaged === id) && (log[i].author === keys.publicKey)) {
// if you've identified someone as something else show that something else
localforage.setItem('image:' + id, log[i].image)
console.log('FINDING IMAGE AND SAVING TO CACHE: ' + log[i].image)
image.src = cache
return image.src = cache
} else if ((log[i].imaged === id) && (log[i].author === id)) {
// else if show the image they gave themselves
localforage.setItem('image:' + id, log[i].image)
console.log('FINDING IMAGE AND SAVING TO CACHE: ' + log[i].image)
image.src = cache
return image.src = cache
}
}
bog().then(log => {
if (log) {
for (var i = 0; i < log.length; i++) {
if ((log[i].imaged === id) && (log[i].author === keys.publicKey)) {
// if you've identified someone as something else show that something else
localforage.setItem('image:' + id, log[i].image)
return image.src = log[i].image
} else if ((log[i].imaged === id) && (log[i].author === id)) {
// else if show the image they gave themselves
localforage.setItem('image:' + id, log[i].image)
return image.src = log[i].image
}
})
}
}
})
return image
@ -137,34 +140,59 @@ function getName (id, keys) {
name.textContent = id.substring(0, 10) + '...'
localforage.getItem('name:' + id).then(cache => {
bog().then(log => {
if (log) {
for (var i = 0; i < log.length; i++ ) {
if ((log[i].named === id) && (log[i].author === keys.publicKey)) {
// if you've identified someone as something else show that something else
localforage.setItem('name:' + id, log[i].name)
return name.textContent = '@' + log[i].name
} else if ((log[i].named === id) && (log[i].author === id)) {
// else if show the name they gave themselves
localforage.setItem('name:' + id, log[i].name)
return name.textContent = '@' + log[i].name
}
// there should probably be some sort of sybil attack resiliance here (weight avatar name based on number of times used by individuals), but this will do for now.
}
}
})
return name
}
function getQuickImage (id, keys) {
var image = h('img', {classList: 'avatar'})
localforage.getItem('image:' + id).then(cache => {
if (cache) {
console.log('GOT NAME FROM CACHE: ' + cache)
return name.textContent = '@' + cache
} else {
bog().then(log => {
if (log) {
for (var i = 0; i < log.length; i++ ) {
if ((log[i].named === id) && (log[i].author === keys.publicKey)) {
// if you've identified someone as something else show that something else
localforage.setItem('name:' + id, log[i].name)
console.log('FINDING NAME AND SAVING TO CACHE: ' + log[i].name)
return name.textContent = '@' + log[i].name
} else if ((log[i].named === id) && (log[i].author === id)) {
// else if show the name they gave themselves
localforage.setItem('name:' + id, log[i].name)
console.log('FINDING NAME AND SAVING TO CACHE: ' + log[i].name)
return name.textContent = '@' + log[i].name
}
// there should probably be some sort of sybil attack resiliance here (weight avatar name based on number of times used by individuals), but this will do for now.
}
}
})
image.src = cache
}
})
return image
}
function getQuickName (id, keys) {
var name = h('span', [id.substring(0, 10)])
localforage.getItem('name:' + id).then(cache => {
if (cache) {
name.textContent = '@' + cache
}
})
return name
}
async function quickName (id, keys) {
var cache = await localforage.getItem('name:' + id)
if (cache) {
return '@' + cache
} else {
return id.substring(0, 10)
}
}
// bog.regenerate -- regenerates main log by taking all of the feed logs, combinging them, and then sorting them
function regenerate (home) {
@ -174,7 +202,7 @@ function regenerate (home) {
if (key[0] == '@') {
newlog = newlog.concat(value)
}
console.log(newlog)
//console.log(newlog)
}).then(function () {
newlog.forEach(function (msg) {
var pubkey = nacl.util.decodeBase64(msg.author.substring(1))
@ -184,7 +212,7 @@ function regenerate (home) {
openedlog.push(opened)
})
console.log(openedlog)
//console.log(openedlog)
openedlog.sort((a, b) => a.timestamp - b.timestamp)
@ -203,7 +231,6 @@ function regenerate (home) {
// 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)


+ 6
- 5
composer.js View File

@ -6,7 +6,7 @@ function composer (keys, reply, gotName, edit) {
console.log(reply)
var textarea = h('textarea', [reply.text])
} else if (gotName) {
var textarea = h('textarea', ['[' + gotName.textContent + '](' + reply.author + ')'])
var textarea = h('textarea', ['[' + gotName + '](' + reply.author + ')'])
} else {
var textarea = h('textarea', {placeholder: 'Verki novan afiŝon... La afiŝo kiun vi verkas ĉi tie aldoniĝos al la bog-reto subskribita per via ŝlosilo. Ĉu vi jam sekure konservis ĝin ie?'})
}
@ -54,8 +54,9 @@ function composer (keys, reply, gotName, edit) {
if (edit) {
console.log('APPENDING EDIT')
var gotit = document.getElementById(reply.key)
gotit.appendChild(h('div', {classList: 'submessage'}, [render(msg, keys)]))
//gotit.appendChild(h('div', {classList: 'submessage'}, [render(msg, keys)]))
var newContent = h('div', {innerHTML: marked(msg.text)})
//console.log(gotit.childNodes.length)
gotit.firstChild.replaceChild(newContent, gotit.firstChild.childNodes[1])
}
if (reply) {
@ -66,15 +67,15 @@ function composer (keys, reply, gotName, edit) {
messageDiv.removeChild(messageDiv.firstChild)
messageDiv = h('div')
messageDiv.appendChild(cache)
scroller.insertBefore(messageDiv, scroller.childNodes[2])
scroller.insertBefore(render(msg, keys), scroller.childNodes[3])
scroller.insertBefore(messageDiv, scroller.childNodes[1])
scroller.insertBefore(render(msg, keys), scroller.childNodes[2])
} else {
messageDiv.appendChild(render(msg, keys))
}
})
})
}
}, ['Publikigi']))
}, ['Afiŝi']))
})
})
}


+ 57
- 12
css/style.css View File

@ -9,7 +9,7 @@ body {
}
p {
margin-top: 1ex;
margin-top: .5ex;
margin-bottom: 1ex;
font-size: 1em;
}
@ -36,35 +36,73 @@ hr {
margin-bottom: .9em;
}
#screen {
position: absolute;
top: 35px;
top: 40px;
right: 0;
left: 0;
bottom: 0;
}
#scroller {
max-width: 50em;
max-width: 680px;
margin-right: auto;
margin-left: auto;
}
@media only screen and (max-width: 480px) {
#screen { top: 57px; right: 5px; left: 5px; }
}
.right { float: right;}
.banner {height: 10px; }
.message, .profile {
border: 1px solid #ddd;
background: white;
margin-top: .5em;
padding: .3em .5em;
}
.message {
margin-top: .5em;
border-radius: 5px;
}
.message, .message > *, .navbar, .navbar > * {
#scroller:last-child { margin-bottom: 10em; }
.message, .message > *, .navbar, .navbar > *, #ad, #ad > *, #viewer > * {
animation: fadein .5s;
}
#ad, #pm {
background: white;
font-size: .8em;
border: 1px solid #ddd;
border-radius: 5px;
position: fixed;
padding: .3em .5em;
bottom: 5px;
width: 250px;
}
#ad {
left: 5px;
}
#pm {
right: 5px;
}
#ad button, #pm button {
font-size: .8em;
margin: 0;
padding: 0;
padding-left: 5px; padding-right: 5px;
position: absolute;
bottom: 2px; right: 2px;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
@ -146,7 +184,7 @@ textarea {
position: fixed;
z-index: 1000;
margin: 0;
padding-top: .3em;
padding-top: .4em;
padding-bottom: .3em;
left: 0; right: 0;
top: 0;
@ -167,17 +205,18 @@ textarea {
}
form.search, input.search {
width: 100px;
width: 175px;
float: right;
margin: 0;
padding: 0;
padding-left: .1em;
color: #f5f5f5;
background: #444;
border-radius: 3px;
}
form.search {
margin-top: .3em;
margin-top: .20em;
}
.navbar li.right {
@ -187,7 +226,6 @@ form.search {
margin-right: 1.7em;
float: right;
list-style-type: none;
background: #333;
border-radius: 100%;
}
@ -202,13 +240,20 @@ form.search {
text-decoration: none;
}
.avatar { width: 25px; height: 25px; vertical-align: middle; border-radius: 5px; margin-right: .2em; }
.profileAvatar { width: 95px; height: 95px; vertical-align: top; border-radius: 5px; object-fit: cover; margin-right: .25em; margin-bottom: .25em;}
.avatar { width: 25px; height: 25px; vertical-align: middle; object-fit: cover; border-radius: 5px; margin-right: .5em; }
.image { width: 75px; height: 75px; object-fit: cover; margin-right: .2em; border-radius: 5px; cursor: pointer;}
#viewer {position: fixed; left: 2em; top: 3em; background: white; padding: .5em; border: 1px solid #ddd; border-radius: 5px;}
#viewer img { border-radius: 5px; margin-left: auto; margin-right: auto; cursor: pointer;}
button {
display: inline-block;
*display: inline;
padding: 4px 12px;
margin-top: 0;
margin-top: 5px;
margin-bottom: 0;
*margin-left: .3em;
font-size: 14px;


+ 175
- 242
gossip.js View File

@ -1,267 +1,200 @@
function blobSync (blob, author, keys, needs) {
console.log(needs)
function processreq (req, pubkey, connection, keys) {
if (req.seq === 0 || req.seq) {
bog(req.author).then(feed => {
if (feed) {
open(feed[0]).then(msg => {
if (req.seq > msg.seq) {
var reqdiff = JSON.stringify({author: req.author, seq: msg.seq})
box(reqdiff, pubkey, keys).then(boxed => {
connection.send(JSON.stringify({
requester: keys.publicKey,
box: boxed
}))
})
}
var wsServers
if (req.seq < msg.seq) {
var endrange = feed.length - req.seq - 25
if (endrange < 0) {
endrange = feed.length - req.seq - 1
}
var baserange = feed.length - req.seq
var diff = JSON.stringify(
feed.slice(
endrange,
baserange)
)
box(diff, pubkey, keys).then(boxed => {
connection.send(JSON.stringify({
requester: keys.publicKey,
box: boxed
}))
})
}
})
}
})
}
// duplicate code, we should abstract this
localforage.getItem('securepubs').then(function (servers) {
if (servers) {
wsServers = servers
} else {
servers = ['wss://interskri.be/ws', 'ws://localhost:8080', 'wss://interskri.be/bogbook', 'ws://bogbook.com']
var pubs = []
servers.forEach(server => {
var ws = new WebSocket(server)
ws.onopen = function () {
ws.send(JSON.stringify({requester: keys.publicKey, sendpub: true}))
}
ws.onmessage = function (message) {
pubs.push(server + '/~' + message.data)
localforage.setItem('securepubs', pubs)
}
if (req.latest) {
var latest
latest = document.getElementById('latest')
var src = window.location.hash.substring(1)
if ((!latest) && (src == req.latest)) {
latest = h('div', {id: 'latest'})
latest.appendChild(h('div', {classList: 'message', innerHTML: marked('**Ankoraŭ sinkronigas la fluon**. Intertempe jen la lastaj kvin mesaĝoj...')
}))
req.feed.forEach(post => {
open(post).then(msg => {
latest.appendChild(render(msg, keys))
})
})
wsServers = pubs
}
}).then(function () {
wsServers.forEach(function (server, index) {
setTimeout(function () {
var split = server.split('~')
var serverurl = split[0]
var serverpub = split[1]
var ws = new WebSocket(serverurl)
scroller.firstChild.appendChild(latest)
console.log('requesting ' + blob + ' from ' + server)
ws.onopen = function () {
var req = {
blob: blob,
needs: needs
}
var timer = setInterval(function () {
localforage.getItem(req.latest).then(feed => {
open(feed[0]).then(msg => {
open(req.feed[0]).then(latestmsg => {
src = window.location.hash.substring(1)
if (msg.seq >= latestmsg.seq) {
latest.parentNode.removeChild(latest)
clearInterval(timer)
//console.log('we are caught up, deleting latest div')
}
if (src != req.latest) {
clearInterval(timer)
//console.log('we navigated away')
}
})
})
})
//console.log('checking to see if we have caught up')
}, 5000)
}
}
box(JSON.stringify(req), serverpub, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
if (Array.isArray(req)) {
open(req[0]).then(msg => {
localforage.getItem(msg.author).then(feed => {
if (!feed) {
localforage.setItem(msg.author, req)
localforage.getItem('log').then(log => {
if (!log) { var log = [] }
for (var i = req.length -1; i >= 0; --i) {
open(req[i]).then(opened => {
log.unshift(opened)
var src = window.location.hash.substring(1)
if ((src === msg.author) || (src === '')) {
var scroller = document.getElementById('scroller')
scroller.insertBefore(render(opened, keys), scroller.childNodes[1])
}
if (opened.seq === req.length) {
localforage.setItem('log', log)
}
})
}
ws.send(JSON.stringify(obj))
})
}
ws.onmessage = function (message) {
var serverreq = JSON.parse(message.data)
unbox(serverreq.box, serverreq.requester, keys).then(unboxed => {
var unboxedreq = JSON.parse(nacl.util.encodeUTF8(unboxed))
//console.log(unboxedreq)
if (unboxedreq.blobFile) {
var openedimg = nacl.sign.open(nacl.util.decodeBase64(unboxedreq.blobFile), nacl.util.decodeBase64(author.substring(1)))
if (openedimg) {
localforage.setItem(unboxedreq.blob, unboxedreq.blobFile)
}
} else {
localforage.getItem(unboxedreq.blob).then(blob => {
if (blob) {
var nextreq = {
author: author,
blob: unboxedreq.blob,
blobFile: blob
}
box(JSON.stringify(nextreq), serverreq.requester, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
}
if (feed) {
open(feed[0]).then(lastmsg => {
if (req.length + lastmsg.seq === msg.seq) {
var newlog = req.concat(feed)
localforage.setItem(msg.author, newlog)
localforage.getItem('log').then(log => {
if (!log) { var log = [] }
for (var i = req.length -1; i >= 0; --i) {
open(req[i]).then(opened => {
log.unshift(opened)
var src = window.location.hash.substring(1)
if ((src === msg.author) || (src === '')) {
var scroller = document.getElementById('scroller')
scroller.insertBefore(render(opened, keys), scroller.childNodes[1])
}
if (req.length + lastmsg.seq === opened.seq) {
localforage.setItem('log', log)
}
ws.send(JSON.stringify(obj))
})
}
})
}
})
}
}, index * 10000)
})
})
})
}
}
function sync (subs, keys) {
function getpubkey (connection, keys) {
connection.onopen = () => {
connection.send(JSON.stringify({
requester: keys.publicKey, sendpub: true
}))
}
var wsServers
connection.onmessage = (m) => {
localforage.setItem(m.origin, m.data)
}
}
localforage.getItem('securepubs').then(function (servers) {
if (servers) {
wsServers = servers
} else {
servers = ['wss://interskri.be/ws', 'ws://localhost:8080', 'wss://interskri.be/bogbook', 'ws://bogbook.com']
var pubs = []
servers.forEach(server => {
var ws = new WebSocket(server)
ws.onopen = function () {
ws.send(JSON.stringify({requester: keys.publicKey, sendpub: true}))
}
ws.onmessage = function (message) {
pubs.push(server + '/~' + message.data)
localforage.setItem('securepubs', pubs)
}
function getfeed (feed, pubkey, connection, keys) {
bog(feed).then(log => {
var logseq = 0
connection.onopen = () => {
if (log) {
open(log[0]).then(msg => {
var string = JSON.stringify(msg)
box(string, pubkey, keys).then(boxed => {
connection.send(JSON.stringify({
requester: keys.publicKey,
box: boxed
}))
})
logseq = msg.seq
})
} else {
var msg = {
author: feed,
seq: logseq
}
box(JSON.stringify(msg), pubkey, keys).then(boxed => {
connection.send(JSON.stringify({
requester: keys.publicKey,
box: boxed
}))
})
}
}
connection.onmessage = (m) => {
var req = JSON.parse(m.data)
unbox(req.box, req.requester, keys).then(unboxed => {
var unboxedreq = JSON.parse(unboxed)
processreq(unboxedreq, pubkey, connection, keys)
})
wsServers = pubs
}
}).then(function () {
subs.forEach(function (sub) {
wsServers.forEach(function (server, index) {
setTimeout(function () {
console.log('SYNCING WITH: ' + server + ' to fetch ' + sub)
var split = server.split('~')
var serverurl = split[0]
var serverpub = split[1]
var ws = new WebSocket(serverurl)
bog(sub).then(srclog => {
if (srclog) {
open(srclog[0]).then(msg => {
ws.onopen = function () {
var message = JSON.stringify(msg)
// if we have a log then send the latest log and see if we get anything back
box(message, serverpub, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
}
ws.send(JSON.stringify(obj))
})
}
ws.onmessage = function (message) {
var req = JSON.parse(message.data)
console.log(req)
unbox(req.box, req.requester, keys).then(unboxed => {
var unboxedreq = JSON.parse(nacl.util.encodeUTF8(unboxed))
console.log(unboxedreq)
if (unboxedreq.seq === 0) {
var stringedfeed = JSON.stringify(srclog)
box(stringedfeed, serverpub, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
}
console.log('Sending entire log of ' + msg.author + ' to ' + serverpub)
console.log(obj)
ws.send(JSON.stringify(obj))
})
}
if (unboxedreq.seq > msg.seq) {
console.log('server feed is longer, requesting diff from server')
var reqdiff = JSON.stringify({author: unboxedreq.author, seq: msg.seq})
box(reqdiff, serverpub, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
}
ws.send(JSON.stringify(obj))
})
}
if (unboxedreq.seq < msg.seq) {
console.log('server feed is shorter, sending diff to server')
var diff = JSON.stringify(srclog.slice(0, msg.seq - unboxedreq.seq))
box(diff, serverpub, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
}
ws.send(JSON.stringify(obj))
})
}
})
}
if (Array.isArray(unboxedreq)) {
console.log('received diff from server')
open(unboxedreq[0]).then(msg => {
localforage.getItem(msg.author).then(feed => {
open(feed[0]).then(lastmsg => {
if (unboxedreq.length + lastmsg.seq === msg.seq) {
var newlog = unboxedreq.concat(feed)
localforage.setItem(msg.author, newlog).then(success => {
console.log('combined existing feed of ' + msg.author + ' with diff and saved to client')
})
localforage.getItem('log').then(log => {
if (!log) {
var log = []
}
for (var i = unboxedreq.length -1; i >= 0; --i) {
open(unboxedreq[i]).then(opened => {
log.unshift(opened)
var scroller = document.getElementById('scroller')
scroller.insertBefore(render(opened, keys), scroller.childNodes[2])
if (unboxedreq.length + lastmsg.seq === opened.seq) {
log.sort((a, b) => a.timestamp - b.timestamp)
var reversed = log.reverse()
localforage.setItem('log', reversed).then(success => {
console.log('saved log with ' + opened.author + ' prepended to localForage')
})
}
})
}
})
}
})
})
})
}
})
}
})
} else {
console.log('NO LOG IN CLIENT')
ws.onopen = function () {
var reqwhole = JSON.stringify({author: sub, seq: 0})
box(reqwhole, serverpub, keys).then(boxed => {
var obj = {
requester: keys.publicKey,
box: boxed
}
ws.send(JSON.stringify(obj))
})
}
ws.onmessage = function (message) {
var req = JSON.parse(message.data)
console.log('received message from ' + req.requester)
unbox(req.box, req.requester, keys).then(unboxed => {
var unboxedreq = JSON.parse(nacl.util.encodeUTF8(unboxed))
if (Array.isArray(unboxedreq)) {
open(unboxedreq[0]).then(msg => {
localforage.getItem(msg.author).then(feed => {
if (!feed) {
localforage.setItem(msg.author, unboxedreq).then(success => {
console.log('saved log of ' + msg.author + ' to localforage')
})
localforage.getItem('log').then(log => {
if (!log) {
var log = []
}
for (var i = unboxedreq.length -1; i >= 0; --i) {
open(unboxedreq[i]).then(opened => {
log.unshift(opened)
var scroller = document.getElementById('scroller')
scroller.insertBefore(render(opened, keys), scroller.childNodes[2])
if (opened.seq === unboxedreq.length) {
log.sort((a, b) => a.timestamp - b.timestamp)
var reversed = log.reverse()
localforage.setItem('log', reversed).then(success => {
console.log('saved log with ' + opened.author + ' prepended to localForage')
})
}
})
}
})
}
})
})
}
})
}
}
})
}, index * 100000)
})
function sync (feeds, keys) {
var pubs
localforage.getItem('pubs').then(pubs => {
if (!pubs) {
pubs = ['ws://' + location.hostname + ':8080', 'ws://bogbook.com']
localforage.setItem('pubs', pubs)
}
pubs.forEach(function (pub, index) {
setTimeout(function () {
var connection = new WebSocket(pub)
localforage.getItem(pub).then(pubkey => {
if (!pubkey) {
getpubkey(connection, keys)
}
if (pubkey) {
feeds.forEach(feed => {
getfeed(feed, pubkey, connection, keys)
})
}
})
}, index * 5000)
})
})
}

+ 206
- 27
identify.js View File

@ -1,21 +1,90 @@
function identify (src, profile, keys) {
function identify (src, profile, keys, name) {
var identifyDiv = h('div')
if (src != keys.publicKey) {
identifyDiv.appendChild(h('p', ['Atentu: ' + src + ' ne estas vi.']))
if ((src[0] == '@') && (src != keys.publicKey)) {
identifyDiv.appendChild(h('p', [
'Atentu: ' + src.substring(0, 10) + '... ne estas vi. Vi estas: ',
h('a', {href: '#' + keys.publicKey}, [keys.publicKey.substring(0, 10) + '...'])
]))
}
var photoURL = {}
// this could be a hell of a lot dry-er
// also we need to get rid of UI glitches when you hit the cancel button (it should return to the same state it started in)
var newBackground = h('span', [
h('input', {id: 'input', type: 'file',
onclick: function () {
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var maxW
var maxH
var input = document.getElementById('input')
input.addEventListener('change', handleFiles)
function handleFiles(e) {
var img = new Image
img.onload = function() {
var iw = img.width
var ih = img.height
maxW = 800
maxH = 800
var scale = Math.min((maxW/iw), (maxH/ih))
var iwScaled = iw*scale
var ihScaled = ih*scale
canvas.width = iwScaled
canvas.height = ihScaled
ctx.drawImage(img, 0, 0, iwScaled, ihScaled)
photoURL.value = canvas.toDataURL('image/jpeg', 0.7)
}
img.src = URL.createObjectURL(e.target.files[0])
}
}
}),
h('canvas', {id: 'canvas', width: '0', height: '0'}),
h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButtons)
newBackground.parentNode.removeChild(newBackground)
}
}, ['Cancel']),
h('button', {
onclick: function () {
if (photoURL.value) {
content = {
type: 'background',
backgrounded: src,
background: photoURL.value
}
localforage.removeItem('image:' + src)
publish(content, keys).then(post => {
open(post).then(msg => {
nameInput.value = ''
scroller.insertBefore(render(msg, keys), scroller.childNodes[1])
})
})
newBackground.parentNode.removeChild(newBackground)
identifyDiv.appendChild(identifyButton)
}
}
}, ['Afiŝi'])
])
var newPhoto = h('span', [
h('input', {id: 'input', type: 'file',
onclick: function () {
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var maxW = 250
var maxH = 250
var maxW
var maxH
var input = document.getElementById('input')
input.addEventListener('change', handleFiles)
@ -24,29 +93,42 @@ function identify (src, profile, keys) {
var img = new Image
img.onload = function() {
var iw = img.width
console.log(iw)
var ih = img.height
console.log(ih)
if (iw > ih) {
maxW = 680
maxH = 500
}
if (iw < ih) {
maxW = 500
maxH = 680
}
if (iw == ih) {
maxW = 500
maxH = 500
}
var scale = Math.min((maxW/iw), (maxH/ih))
var iwScaled = iw*scale
var ihScaled = ih*scale
canvas.width = iwScaled
canvas.height = ihScaled
ctx.drawImage(img, 0, 0, iwScaled, ihScaled)
console.log(canvas.toDataURL('image/jpeg', 0.9))
photoURL.value = canvas.toDataURL('image/jpeg', 0.9)
//identifyDiv.appendChild(h('img', {src: photoURL.value}))
}
img.src = URL.createObjectURL(e.target.files[0])
}
}
}),
h('p', ['Ni rekomendas alŝuti kvadratan foton. Ĝi aŭtomate tranĉiĝas al 250 je 250 px.']),
h('canvas', {id: 'canvas', width: '0', height: '0'}),
h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButtons)
newPhoto.parentNode.removeChild(newPhoto)
}
}, ['Cancel']),
}, ['Nuligi']),
h('button', {
onclick: function () {
if (photoURL.value) {
@ -55,7 +137,6 @@ function identify (src, profile, keys) {
imaged: src,
image: photoURL.value
}
console.log(content)
localforage.removeItem('image:' + src)
publish(content, keys).then(post => {
open(post).then(msg => {
@ -67,7 +148,69 @@ function identify (src, profile, keys) {
identifyDiv.appendChild(identifyButton)
}
}
}, ['Publikigi'])
}, ['Afiŝi'])
])
var descInput = h('textarea', {placeholder: 'Nova priskribo'})
var newDescription = h('div', [
descInput,
h('button', {
onclick: function () {
if (descInput.value) {
content = {
type: 'description',
descripted: src,
description: descInput.value
}
publish(content, keys).then(post => {
open(post).then(msg => {
descInput.value = ''
scroller.insertBefore(render(msg, keys), scroller.childNodes[1])
})
})
newDescription.parentNode.removeChild(newDescription)
identifyDiv.appendChild(identifyButton)
}
}
}, ['Afiŝi']),
h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButtons)
newDescription.parentNode.removeChild(newDescription)
}
}, ['Nuligi'])
])
var locInput = h('input', {placeholder: 'Nova loko'})
var newLocation = h('div', [
locInput,
h('button', {
onclick: function () {
if (locInput.value) {
content = {
type: 'location',
located: src,
loc: locInput.value
}
publish(content, keys).then(post => {
open(post).then(msg => {
locationInput.value = ''
scroller.insertBefore(render(msg, keys), scroller.childNodes[1])
})
})
newLocation.parentNode.removeChild(newLocation)
identifyDiv.appendChild(identifyButton)
}
}
}, ['Afiŝi']),
h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButtons)
newLocation.parentNode.removeChild(newLocation)
}
}, ['Nuligi'])
])
var nameInput = h('input', {placeholder: 'Nova nomo'})
@ -93,7 +236,7 @@ function identify (src, profile, keys) {
identifyDiv.appendChild(identifyButton)
}
}
}, ['Publikigi']),
}, ['Afiŝi']),
h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButtons)
@ -102,35 +245,71 @@ function identify (src, profile, keys) {
}, ['Nuligi'])
])
var identifyButtons = h('span', [
h('button', {
var identifyButtons = h('span')
if (src[0] == '@') {
identifyButtons.appendChild(h('button', {
onclick: function () {
identifyDiv.appendChild(newName)
identifyButtons.parentNode.removeChild(identifyButtons)
}
}, ['Identigi ' + src.substring(0, 10) + '... per nova nomo']),
h('button', {
}, ['Nova nomo']))
identifyButtons.appendChild(h('button', {
onclick: function () {
identifyDiv.appendChild(newPhoto)
identifyDiv.appendChild(newBackground)
identifyButtons.parentNode.removeChild(identifyButtons)
}
}, ['Identigi ' + src.substring(0, 10) + '... per nova foto']),
h('button', {
}, ['Nova fono']))
identifyButtons.appendChild(h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButton)
identifyDiv.appendChild(newDescription)
identifyButtons.parentNode.removeChild(identifyButtons)
}
}, ['Nuligi'])
])
}, ['Nova priskribo']))
var identifyButton = h('button', {
}
//}, ['Identify ' + src.substring(0, 10) + '... with a new name']),
identifyButtons.appendChild(h('button', {
onclick: function () {
profile.appendChild(identifyDiv)
profile.appendChild(identifyButtons)
identifyButton.parentNode.removeChild(identifyButton)
identifyDiv.appendChild(newPhoto)
identifyButtons.parentNode.removeChild(identifyButtons)
}
},['Identigi ' + src.substring(0, 10) + '...'])
}, ['Nova bildo']))
identifyButtons.appendChild(h('button', {
onclick: function () {
identifyDiv.appendChild(newLocation)
identifyButtons.parentNode.removeChild(identifyButtons)
}
}, ['Nova loko']))
//}, ['Identify ' + src.substring(0, 10) + '... with a new photo']),
identifyButtons.appendChild(h('button', {
onclick: function () {
identifyDiv.appendChild(identifyButton)
identifyButtons.parentNode.removeChild(identifyButtons)
}
}, ['Nuligi']))
/*if (src[0] == '@') {
var identifyButton = h('button', {
onclick: function () {
profile.appendChild(identifyDiv)
profile.appendChild(identifyButtons)
identifyButton.parentNode.removeChild(identifyButton)
}
}, ['Identify ' + name])
} else { */
var identifyButton = h('button', {
onclick: function () {
profile.appendChild(identifyDiv)
profile.appendChild(identifyButtons)
identifyButton.parentNode.removeChild(identifyButton)
}
//}, ['Add to ' + src.substring(0, 10) + '...'])
}, ['+'])
//}
return identifyButton
}

+ 2
- 1
index.html View File

@ -16,9 +16,10 @@
<script src="./lib/misc.js"></script>
<script src="bog.js"></script>
<script src="composer.js"></script>
<script src="settings.js"></script>
<script src="identify.js"></script>
<script src="gossip.js"></script>
<script src="render.js"></script>
<script src="gossip.js"></script>
<script src="views.js"></script>
<script src="app.js"></script>
</body>


+ 366
- 51
package-lock.json View File

@ -1,32 +1,99 @@
{
"name": "bogbook",
"version": "1.5.0",
"version": "1.8.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
},
"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=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"charset": {
"cache-content-type": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz",
"integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg=="
},
"ecstatic": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.2.tgz",
"integrity": "sha512-lnrAOpU2f7Ra8dm1pW0D1ucyUxQIEk8RjFrvROg1YqCV0ueVu9hzgiSEbSyROqXDDiHREdqC4w3AwOTb23P4UQ==",
"requires": {
"charset": "^1.0.1",
"he": "^1.1.1",
"mime": "^2.4.1",
"minimist": "^1.1.0",
"on-finished": "^2.3.0",
"url-join": "^4.0.0"
"resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
"integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
"requires": {
"mime-types": "^2.1.18",
"ylru": "^1.2.0"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookies": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz",
"integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==",
"requires": {
"depd": "~2.0.0",
"keygrip": "~1.1.0"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ed2curve": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.2.1.tgz",
@ -47,25 +114,187 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"error-inject": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz",
"integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-assert": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz",
"integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==",
"requires": {
"deep-equal": "~1.0.1",
"http-errors": "~1.7.2"
}
},
"http-errors": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-generator-function": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz",
"integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw=="
},
"is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
"integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
},
"mime": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz",
"integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw=="
"keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
"integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
"requires": {
"tsscmp": "1.0.6"
}
},
"koa": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz",
"integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==",
"requires": {
"accepts": "^1.3.5",
"cache-content-type": "^1.0.0",
"content-disposition": "~0.5.2",
"content-type": "^1.0.4",
"cookies": "~0.8.0",
"debug": "~3.1.0",
"delegates": "^1.0.0",
"depd": "^1.1.2",
"destroy": "^1.0.4",
"encodeurl": "^1.0.2",
"error-inject": "^1.0.0",
"escape-html": "^1.0.3",
"fresh": "~0.5.2",
"http-assert": "^1.3.0",
"http-errors": "^1.6.3",
"is-generator-function": "^1.0.7",
"koa-compose": "^4.1.0",
"koa-convert": "^1.2.0",
"on-finished": "^2.3.0",
"only": "~0.0.2",
"parseurl": "^1.3.2",
"statuses": "^1.5.0",
"type-is": "^1.6.16",
"vary": "^1.1.2"
}
},
"koa-compose": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
"integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw=="
},
"minimist": {
"koa-convert": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz",
"integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=",
"requires": {
"co": "^4.6.0",
"koa-compose": "^3.0.0"
},
"dependencies": {
"koa-compose": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz",
"integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=",
"requires": {
"any-promise": "^1.1.0"
}
}
}
},
"koa-send": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.0.tgz",
"integrity": "sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ==",
"requires": {
"debug": "^3.1.0",
"http-errors": "^1.6.3",
"mz": "^2.7.0",
"resolve-path": "^1.4.0"
}
},
"koa-static-server": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/koa-static-server/-/koa-static-server-1.4.0.tgz",
"integrity": "sha512-ZkGzD9+2OZubOL458GL8p7vEGfaneGGKSKIUJw9ftXzn36capQw3Wu5A//o7S10tQNgD1oYs96LGPtqtkcuXbQ==",
"requires": {
"koa-send": "^5.0.0",
"upath": "^1.0.2"
}
},