~ev/oldbogbookv1

ed171c40f7d742af3020b60eac0e3ba3f5d7514c — Ev Bogue 2 years ago 908213f
revert to last known working commit
14 files changed, 595 insertions(+), 801 deletions(-)

M app.js
M bog.js
M composer.js
M gossip.js
M identify.js
M index.html
D lib/lightning-fs.min.js
A lib/localforage.min.js
M package-lock.json
M package.json
M render.js
M server.js
M settings.js
M views.js
M app.js => app.js +0 -7
@@ 1,13 1,6 @@
var screen = h('div', {id: 'screen'})
document.body.appendChild(screen)

var masterlog = []

readBog().then(log => {
  masterlog = log
})


function route (keys) {
  src = window.location.hash.substring(1)


M bog.js => bog.js +148 -141
@@ 1,26 1,13 @@

// so bog.js works in node.js and the browser
if ((typeof process !== 'undefined') && (process.release.name === 'node')) {
  // We are on the server
  var fs = require('fs')
  var pfs = fs.promises
  var nacl = require('tweetnacl')
      nacl.util = require('tweetnacl-util')
  var ed2curve = require('ed2curve')
  var bogdir = require('os').homedir() + '/.bogbook/'

} else {
  // We are in the browser
  var fs = new LightningFS('bogbook')
  var pfs = fs.promises
  var bogdir = '/'
  var homedir = require('os').homedir()
}

pfs.readdir(bogdir + 'bogs/').then(result => {
  //console.log(result)
}).catch(error => {
  pfs.mkdir(bogdir + 'bogs/')
})

// 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) })



@@ 42,6 29,7 @@ function generatekey () {
  var keypair = {
    publicKey : '@/'
  }
  console.log('generating new keypair')
  while (keypair.publicKey.includes('/')) {
    var genkey = nacl.sign.keyPair()
    keypair.publicKey = '@' + nacl.util.encodeBase64(genkey.publicKey),


@@ 52,10 40,23 @@ function generatekey () {

async function keys () {
  try {
    var keypair = JSON.parse(await pfs.readFile(bogdir + 'keypair', 'utf8'))
  } catch {
    keypair = generatekey()
    await pfs.writeFile(bogdir + 'keypair', JSON.stringify(keypair), 'utf8')
    if (fs) {
      var keypair = JSON.parse(fs.readFileSync(homedir + '/.bogbook/keypair'))
    } else {
      var keypair = await localforage.getItem('id')
      if (keypair === null) {
        var keypair = generatekey()
        localforage.setItem('id', keypair)
      }
    }
  } catch (err) {
    var keypair = generatekey()
    if (fs) {
      if (!fs.existsSync(homedir + '/.bogbook')){
        fs.mkdirSync(homedir + '/.bogbook')
      }
      fs.writeFileSync(homedir + '/.bogbook/keypair', JSON.stringify(keypair), 'UTF-8')
    }
  }
  return keypair
}


@@ 83,19 84,29 @@ async function unbox (boxed, sender, keys) {
// EX: get('%x5T7KZ5haR2F59ynUuCggwEdFXlLHEtFoBQIyKYppZYerq9oMoIqH76YzXQpw2DnYiM0ugEjePXv61g3E4l/Gw==').then(msg => { console.log(msg)})

async function get (key) {
  //var log = await readBog()
  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]
      }
    }
  }
}

  for (var i = masterlog.length - 1; i >= 0; --i) {
    if (masterlog[i].key === key) {
      return masterlog[i]
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

var nameCache = []
var imageCache = []
// bog.getImage

function getImage (id, keys, classList) {
  if (classList) {


@@ 104,22 115,21 @@ function getImage (id, keys, classList) {
    var image = h('img', {classList: 'avatar'})
  }

  if (imageCache.find(x => x.id === id)) {
    var foundObj = imageCache.find(x => x.id === id)
    return image.src = foundObj.image
  } else {
    for (var i = 0; i < masterlog.length; i++) {
      if ((masterlog[i].imaged === id) && (masterlog[i].author === keys.publicKey)) {
        // if you've identified someone as something else show that something else
        imageCache.push({id: id, image: masterlog[i].image})
        return image.src = masterlog[i].image
      } else if ((masterlog[i].imaged === id) && (masterlog[i].author === id)) {
        // else if show the image they gave themselves
        imageCache.push({id: id, image: masterlog[i].image})
        return image.src = masterlog[i].image
  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
}



@@ 130,124 140,109 @@ function getName (id, keys) {

  name.textContent = id.substring(0, 10) + '...'

  if (nameCache.find(x => x.id === id)) {
    var foundObj = nameCache.find(x => x.id === id)
    return name.textContent = '@' + foundObj.name
  } else {
    for (var i = 0; i < masterlog.length; i++ ) {
      if ((masterlog[i].named === id) && (masterlog[i].author === keys.publicKey)) {
        // if you've identified someone as something else show that something else
        nameCache.push({id: id, name: masterlog[i].name})
        return name.textContent = '@' + masterlog[i].name
      } else if ((masterlog[i].named === id) && (masterlog[i].author === id)) {
        // else if show the name they gave themselves
        nameCache.push({id: id, name: masterlog[i].name})
        return name.textContent = '@' + masterlog[i].name
  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.
      }
      // 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
}

async function quickName (id, keys) {
  try { 
    var foundObj = nameCache.find(x => x.id === id) 
    return '@' + foundObj.name
  } catch {
    return id.substring(0, 10)
  }
}
function getQuickImage (id, keys) {
  var image = h('img', {classList: 'avatar'})

async function removefeed (src) {
  if (src[0] == '@') {
    await pfs.unlink(bogdir + 'bogs/' + src)
  } else {
    await pfs.unlink(bogdir + src)
  }
  localforage.getItem('image:' + id).then(cache => {
    if (cache) {
      image.src = cache
    }
  })

  return image
}

async function removeall () {
  var bogs = await(pfs.readdir(bogdir + 'bogs/'))
function getQuickName (id, keys) {
  var name = h('span', [id.substring(0, 10)])

  for (i = 0; i < bogs.length; i++) {
    await pfs.unlink(bogdir + 'bogs/' + bogs[i])
  }
  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)

  var array = await(pfs.readdir(bogdir))
  for (i = 0; i < array.length; i++) {
    await pfs.unlink(bogdir + array[i])
  if (cache) {
    return '@' + cache
  } else {
    return id.substring(0, 10)
  }
}

// bog.regenerate -- regenerates main log by taking all of the feed logs, combining them, and then sorting them -- this is only run when you delete a feed these days

async function regenerate () {
function regenerate (home) {
  var newlog = []
  var openedlog = []

  var array = await(pfs.readdir(bogdir + 'bogs/'))

  for (i = 0; i < array.length; i++) {
    var name = array[i]
    console.log(name[0])
    if(name[0] == '@') {
      var value = await readBog(name)
      console.log(value)
  localforage.iterate(function(value, key, i) {
    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))
      var sig = nacl.util.decodeBase64(msg.signature)
      var opened = JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
      opened.key = msg.key

      openedlog.push(opened)
    })
    //console.log(openedlog)

  newlog.forEach(function (msg) {
    console.log(msg)
    var pubkey = nacl.util.decodeBase64(msg.author.substring(1))
    var sig = nacl.util.decodeBase64(msg.signature)
    var opened = JSON.parse(nacl.util.encodeUTF8(nacl.sign.open(sig, pubkey)))
    opened.key = msg.key
    openedlog.sort((a, b) => a.timestamp - b.timestamp)

    openedlog.push(opened)
    var reversed = openedlog.reverse()
    console.log('REGENERATE')
    localforage.setItem('log', reversed).then(function () {
      if (home) {
        location.hash = ''
      }
      location.reload()
    })
  })

  openedlog.sort((a, b) => a.timestamp - b.timestamp)

  var reversed = openedlog.reverse()
  await writeBog('log', reversed)
  return
}

// readBog (feed) -- returns a specific feed if a parameter is passed
// EX: bog('log').then(log => { console.log(log)})
// 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 readBog (feed) {
  if (!feed) { var feed = 'log' }
  if (feed[0] == '@') {
    try {
      var log = JSON.parse(await pfs.readFile(bogdir + 'bogs/' + feed, 'utf8'))
    } catch {
      var log = [] 
    }
  } else {
    try {
      var log = JSON.parse(await pfs.readFile(bogdir + feed, 'utf8'))
    } catch {
      var log = [] 
    }
  }
  return log
}

async function writeBog (feed, log) {
  if (feed[0] == '@') { 
    await pfs.writeFile(bogdir + 'bogs/'+ feed, JSON.stringify(log), 'utf8')
async function bog (feed) {
  if (feed) {
    var log = await localforage.getItem(feed)
    return log
  } else {
    await pfs.writeFile(bogdir + feed, JSON.stringify(log), 'utf8')
    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'}, keys).then(msg => { console.log(msg)})
// EX: publish({type: 'post', timestamp: Date.now(), text: 'Hello World'}).then(msg => { console.log(msg)})

async function publish (post, keys, preview) {
  post.author = keys.publicKey


@@ 256,28 251,43 @@ async function publish (post, keys, preview) {
  var message = { 
    author: keys.publicKey 
  }
  
  var feed = await localforage.getItem(keys.publicKey)

  var feed = await readBog(keys.publicKey)

  if (feed[0]) {
  if (feed) {
    var firstMsg = await open(feed[0])
    post.seq = ++firstMsg.seq
  } else {
    post.seq = 1
  }

  // we need to change the key to a shorter hash such as sha2 or blake2
  message.key = '%' + nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(JSON.stringify(post))))
  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)))

  var openedMsg = await open(message)

  if (!preview) {
    readBog().then(log => {
      log.unshift(openedMsg)
      writeBog('log', log)
    console.log('ADDING TO LOG AND FEED')
    localforage.getItem('log').then(log => {
      if (log) {
        log.unshift(openedMsg)
        localforage.setItem('log', log)
      } else {
        var newlog = [openedMsg]
        localforage.setItem('log', newlog)
      }
    })

    var subs = [keys.publicKey]

    if (feed) {
      feed.unshift(message)
      writeBog(keys.publicKey, feed)  
    } else {
      var feed = [message]
    }

    localforage.setItem(keys.publicKey, feed).then(function () {
      sync(subs, keys)    
    })
  }
  return message


@@ 288,9 298,6 @@ if ((typeof process !== 'undefined') && (process.release.name === 'node')) {
    keys,
    open,
    box,
    unbox,
    regenerate,
    readBog,
    writeBog
    unbox
  }
}

M composer.js => composer.js +1 -47
@@ 1,47 1,3 @@
function contacts (textarea, keys) {
  var contacts = h('span')

  var div = h('p')

  var close = h('button', {
    onclick: function () {
      div.parentNode.removeChild(div)
      div = h('p')
      close.parentNode.removeChild(close)
      contacts.appendChild(button)
    }
  }, ['- Contacts'])
 
  var button = h('button', {
    onclick: function () {
      button.parentNode.removeChild(button)
      contacts.appendChild(close)
      contacts.appendChild(div)
      readBog('subscriptions').then(function (subs) {
        subs.forEach(sub => {
          var name = getName(sub, keys)
          div.appendChild(h('div', [
            h('a', {href: '#' + sub}, [
              getImage(sub, keys),
              name
            ]),
            ' ',
            h('button', {
              onclick: function () {
                textarea.value = textarea.value + ' [' + name.textContent + '](' + sub + ')'
              }
            }, ['Add'])
          ]))
        })
      })
    }
  },['Contacts']) 

  contacts.appendChild(button)

  return contacts
}

function composer (keys, reply, gotName, edit) {
  var messageDiv = h('div')
  var message = h('div', {classList: 'message'})


@@ 79,7 35,6 @@ function composer (keys, reply, gotName, edit) {
          }

          publish(content, keys, {preview: true}).then(post => {
            console.log('rendering preview in browser')
            open(post).then(msg => {
              var preview = render(msg, keys, {preview: true})
              var cache = messageDiv.firstChild


@@ 125,8 80,7 @@ function composer (keys, reply, gotName, edit) {
          })
        }
      }
    }, ['Preview']),
    contacts(textarea, keys)
    }, ['Preview'])
  ])

  message.appendChild(publisher)

M gossip.js => gossip.js +177 -124
@@ 1,149 1,202 @@
function getfeed (feed, pub, keys) {
  function reqfeed (seq, log) {
    var memo = feed + seq
    console.log('CLIENT:' + memo)
    box(memo, pub.pubkey, keys).then(boxed => {
      var es = new EventSource(keys.publicKey + boxed) 
      es.addEventListener('message', (e) => {
        var pubkey = e.data.substring(0, 45)
        var reqboxed = e.data.substring(45)
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
              }))
            })
          }

        unbox(reqboxed, pubkey, keys).then(unboxed => {
          if (unboxed[0] === '@') {
            console.log('SERVER:' + unboxed)
            var reqpub = unboxed.substring(0, 45)
            var serverseq = unboxed.substring(45)
            if (serverseq < seq) {
              // send more feed to the server 
              console.log('SEND MORE FEED TO SERVER')
              var endrange = log.length - serverseq - 25
              if (endrange < 0) {
                endrange = log.length - serverseq - 1
              }
              var baserange = log.length - serverseq
              var diff = JSON.stringify(log.slice(endrange,baserange))
              box(diff, pub.pubkey, keys).then(nextboxed => {
                var newes = new EventSource(keys.publicKey + nextboxed)
                //newes.close()
              })
          if (req.seq < msg.seq) {
            var endrange = feed.length - req.seq - 25
            if (endrange < 0) {
              endrange = feed.length - req.seq - 1
            }
            else {
              es.close()
            }
          } 
            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
              }))
            })
          }
        })
      }
    })
  }

          if (unboxed[0] === '[') {
            console.log('RECEIVED MORE FEED FROM SERVER')
            var newfeed = JSON.parse(unboxed)
            console.log(newfeed)
  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('**Still syncing feed**. In the meantime, here are the latest five messages...')
      }))
      req.feed.forEach(post => {
        open(post).then(msg => {
          latest.appendChild(render(msg, keys))
        })
      })
      scroller.firstChild.appendChild(latest)

            open(newfeed[0]).then(msg => {
              readBog(msg.author).then(feed => {
                if (!feed[0]) {
                  writeBog(msg.author, newfeed)
                  for (var i = newfeed.length -1; i >= 0; --i) {
                    open(newfeed[i]).then(opened => {
                      console.log('new msg from ' + opened.author)
                      masterlog.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])
                      }
      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)
    }
  }

                      if (opened.seq === newfeed.length) {
                        writeBog('log', masterlog)
                        es.close()
                      }
                    })
                  }
  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 (feed[0]) {
                  open(feed[0]).then(lastmsg => {
                    if (newfeed.length + lastmsg.seq === msg.seq) {
                      var newlog = newfeed.concat(feed)
                      writeBog(msg.author, newlog)
                      for (var i = newfeed.length -1; i >= 0; --i) {
                        open(newfeed[i]).then(opened => {
                          console.log('new msg from ' + opened.author)
                          masterlog.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 (newfeed.length + lastmsg.seq === opened.seq) {
                            writeBog('log', masterlog)
                            es.close()
                          }
                        })
                      }

                if (opened.seq === req.length) {
                  localforage.setItem('log', log)
                }
              })
            }
          })
        } 
        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)
                    }
                  })
                }
              })
              for (var i = newfeed.length -1; i >= 0; --i) {
                open(newfeed[i]).then(opened => {
                  console.log('new msg from ' + opened.author)
                  masterlog.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 === newfeed.length) {
                    writeBog('log', masterlog)
                    es.close()
                  }
                })
              }
            })
          } else {es.close()}
        })
        es.close()
            }
          })
        }
      })
    })
  }
}

function getpubkey (connection, keys) {
  connection.onopen = () => {
    connection.send(JSON.stringify({
      requester: keys.publicKey, sendpub: true
    }))
  }

  connection.onmessage = (m) => {
    localforage.setItem(m.origin, m.data)
  }
}

  readBog(feed).then(log => {
    if (!log[0]) {
      var seq = 0
      reqfeed(seq, log)
    } else {
      open(log[0]).then(opened => {
        var seq = opened.seq
        reqfeed(seq, log)
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)
      })
    } 
    }
  })
}

function sync (feeds, keys) {
  readBog('isopubs').then(pubs => {
    if (!pubs[0]) {
      pubs = [
        {ws: 'ws://' + location.hostname + ':8080'}, 
        {ws: 'ws://bogbook.com'}
      ]
      writeBog('isopubs', pubs)
  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) {
      if (!pub.pubkey) {
        var es = new EventSource('/pubkey')
        es.addEventListener('message', (e) => {
          pubs[index].pubkey = e.data
          writeBog('isopubs', pubs)
          es.close()
        })
      } else {
        feeds.forEach(feed => {
          getfeed(feed, pub, keys)
      setTimeout(function () {
        console.log(pub)
        var connection = new WebSocket(pub)
        localforage.getItem(pub).then(pubkey => {
          if (!pubkey) {
            getpubkey(connection, keys)
          }
          if (pubkey) {
            feeds.forEach(feed => {
              console.log(feeds)
              getfeed(feed, pubkey, connection, keys)
            })
          }
        })
      }
      }, index * 5000)
    })
  })
}

M identify.js => identify.js +3 -3
@@ 63,7 63,7 @@ function identify (src, profile, keys, name) {
            backgrounded: src,
            background: photoURL.value
          }
          removefeed('image:' + src)
          localforage.removeItem('image:' + src)
          publish(content, keys).then(post => {
            open(post).then(msg => {
              nameInput.value = ''


@@ 137,7 137,7 @@ function identify (src, profile, keys, name) {
            imaged: src,
            image: photoURL.value
          }
          removefeed('image:' + src)
          localforage.removeItem('image:' + src)
          publish(content, keys).then(post => {
            open(post).then(msg => {
              nameInput.value = ''


@@ 225,7 225,7 @@ function identify (src, profile, keys, name) {
            named: src,
            name: nameInput.value
          }
          removefeed('name:' + src)
          localforage.removeItem('name:' + src)
          publish(content, keys).then(post => {
            open(post).then(msg => {
              nameInput.value = ''

M index.html => index.html +1 -1
@@ 7,10 7,10 @@
    <link rel='stylesheet' href='./css/style.css' />
  </head>
  <body>
    <script src="./lib/lightning-fs.min.js"></script>
    <script src="./lib/nacl.min.js"></script>
    <script src="./lib/nacl-util.min.js"></script>
    <script src="./lib/ed2curve.min.js"></script>
    <script src="./lib/localforage.min.js"></script>
    <script src="./lib/diff.js"></script>
    <script src="./lib/marked.min.js"></script>
    <script src="./lib/misc.js"></script>

D lib/lightning-fs.min.js => lib/lightning-fs.min.js +0 -1
@@ 1,1 0,0 @@
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.LightningFS=e():t.LightningFS=e()}(self,function(){return function(t){var e={};function i(r){if(e[r])return e[r].exports;var n=e[r]={i:r,l:!1,exports:{}};return t[r].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=t,i.c=e,i.d=function(t,e,r){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)i.d(r,n,function(e){return t[e]}.bind(null,n));return r},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=3)}([function(t,e){function i(t){if(0===t.length)return".";let e=n(t);return e=e.reduce(s,[]),r(...e)}function r(...t){if(0===t.length)return"";let e=t.join("/");return e=e.replace(/\/{2,}/g,"/")}function n(t){if(0===t.length)return[];if("/"===t)return["/"];let e=t.split("/");return""===e[e.length-1]&&e.pop(),"/"===t[0]?e[0]="/":"."!==e[0]&&e.unshift("."),e}function s(t,e){if(0===t.length)return t.push(e),t;if("."===e)return t;if(".."===e){if(1===t.length){if("/"===t[0])throw new Error("Unable to normalize path - traverses above root directory");if("."===t[0])return t.push(e),t}return".."===t[t.length-1]?(t.push(".."),t):(t.pop(),t)}return t.push(e),t}t.exports={join:r,normalize:i,split:n,basename:function(t){if("/"===t)throw new Error(`Cannot get basename of "${t}"`);const e=t.lastIndexOf("/");return-1===e?t:t.slice(e+1)},dirname:function(t){const e=t.lastIndexOf("/");if(-1===e)throw new Error(`Cannot get dirname of "${t}"`);return 0===e?"/":t.slice(0,e)},resolve:function(...t){let e="";for(let n of t)e=n.startsWith("/")?n:i(r(e,n));return e}}},function(t,e){function i(t){return class extends Error{constructor(...e){super(...e),this.code=t,this.message?this.message=t+": "+this.message:this.message=t}}}const r=i("EEXIST"),n=i("ENOENT"),s=i("ENOTDIR"),o=i("ENOTEMPTY"),a=i("ETIMEDOUT");t.exports={EEXIST:r,ENOENT:n,ENOTDIR:s,ENOTEMPTY:o,ETIMEDOUT:a}},function(t,e,i){"use strict";i.r(e),i.d(e,"Store",function(){return r}),i.d(e,"get",function(){return o}),i.d(e,"set",function(){return a}),i.d(e,"update",function(){return h}),i.d(e,"del",function(){return c}),i.d(e,"clear",function(){return l}),i.d(e,"keys",function(){return u}),i.d(e,"close",function(){return d});class r{constructor(t="keyval-store",e="keyval"){this.storeName=e,this._dbName=t,this._storeName=e,this._init()}_init(){this._dbp||(this._dbp=new Promise((t,e)=>{const i=indexedDB.open(this._dbName);i.onerror=(()=>e(i.error)),i.onsuccess=(()=>t(i.result)),i.onupgradeneeded=(()=>{i.result.createObjectStore(this._storeName)})}))}_withIDBStore(t,e){return this._init(),this._dbp.then(i=>new Promise((r,n)=>{const s=i.transaction(this.storeName,t);s.oncomplete=(()=>r()),s.onabort=s.onerror=(()=>n(s.error)),e(s.objectStore(this.storeName))}))}_close(){return this._init(),this._dbp.then(t=>{t.close(),this._dbp=void 0})}}let n;function s(){return n||(n=new r),n}function o(t,e=s()){let i;return e._withIDBStore("readwrite",e=>{i=e.get(t)}).then(()=>i.result)}function a(t,e,i=s()){return i._withIDBStore("readwrite",i=>{i.put(e,t)})}function h(t,e,i=s()){return i._withIDBStore("readwrite",i=>{const r=i.get(t);r.onsuccess=(()=>{i.put(e(r.result),t)})})}function c(t,e=s()){return e._withIDBStore("readwrite",e=>{e.delete(t)})}function l(t=s()){return t._withIDBStore("readwrite",t=>{t.clear()})}function u(t=s()){const e=[];return t._withIDBStore("readwrite",t=>{(t.openKeyCursor||t.openCursor).call(t).onsuccess=function(){this.result&&(e.push(this.result.key),this.result.continue())}}).then(()=>e)}function d(t=s()){return t._close()}},function(t,e,i){const r=i(4),n=i(5);function s(t,e){"function"==typeof t&&(e=t);return[(...t)=>e(null,...t),e=r(e)]}t.exports=class{constructor(...t){this.promises=new n(...t),this.readFile=this.readFile.bind(this),this.writeFile=this.writeFile.bind(this),this.unlink=this.unlink.bind(this),this.readdir=this.readdir.bind(this),this.mkdir=this.mkdir.bind(this),this.rmdir=this.rmdir.bind(this),this.rename=this.rename.bind(this),this.stat=this.stat.bind(this),this.lstat=this.lstat.bind(this),this.readlink=this.readlink.bind(this),this.symlink=this.symlink.bind(this),this.backFile=this.backFile.bind(this)}init(t,e){this.promises.init(t,e)}readFile(t,e,i){const[r,n]=s(e,i);this.promises.readFile(t,e).then(r).catch(n)}writeFile(t,e,i,r){const[n,o]=s(i,r);this.promises.writeFile(t,e,i).then(n).catch(o)}unlink(t,e,i){const[r,n]=s(e,i);this.promises.unlink(t,e).then(r).catch(n)}readdir(t,e,i){const[r,n]=s(e,i);this.promises.readdir(t,e).then(r).catch(n)}mkdir(t,e,i){const[r,n]=s(e,i);this.promises.mkdir(t,e).then(r).catch(n)}rmdir(t,e,i){const[r,n]=s(e,i);this.promises.rmdir(t,e).then(r).catch(n)}rename(t,e,i){const[r,n]=s(i);this.promises.rename(t,e).then(r).catch(n)}stat(t,e,i){const[r,n]=s(e,i);this.promises.stat(t).then(r).catch(n)}lstat(t,e,i){const[r,n]=s(e,i);this.promises.lstat(t).then(r).catch(n)}readlink(t,e,i){const[r,n]=s(e,i);this.promises.readlink(t).then(r).catch(n)}symlink(t,e,i){const[r,n]=s(i);this.promises.symlink(t,e).then(r).catch(n)}backFile(t,e,i){const[r,n]=s(e,i);this.promises.backFile(t,e).then(r).catch(n)}}},function(t,e){t.exports=function(t){var e,i;if("function"!=typeof t)throw new Error("expected a function but got "+t);return function(){return e?i:(e=!0,i=t.apply(this,arguments))}}},function(t,e,i){const{encode:r,decode:n}=i(6),s=i(9),o=i(10),a=i(11),{ENOENT:h,ENOTEMPTY:c,ETIMEDOUT:l}=i(1),u=i(12),d=i(13),_=i(14),p=i(15),m=i(0);i(16);function f(t,e){return void 0!==e&&"function"!=typeof e||(e={}),"string"==typeof e&&(e={encoding:e}),[t=m.normalize(t),e]}function w(t,e){return[m.normalize(t),m.normalize(e)]}t.exports=class{constructor(t,e){this.init=this.init.bind(this),this.readFile=this._wrap(this.readFile,!1),this.writeFile=this._wrap(this.writeFile,!0),this.unlink=this._wrap(this.unlink,!0),this.readdir=this._wrap(this.readdir,!1),this.mkdir=this._wrap(this.mkdir,!0),this.rmdir=this._wrap(this.rmdir,!0),this.rename=this._wrap(this.rename,!0),this.stat=this._wrap(this.stat,!1),this.lstat=this._wrap(this.lstat,!1),this.readlink=this._wrap(this.readlink,!1),this.symlink=this._wrap(this.symlink,!0),this.backFile=this._wrap(this.backFile,!0),this.saveSuperblock=s(()=>{this._saveSuperblock()},500),this._deactivationPromise=null,this._deactivationTimeout=null,this._activationPromise=null,this._operations=new Set,t&&this.init(t,e)}async init(...t){return this._initPromiseResolve&&await this._initPromise,this._initPromise=this._init(...t),this._initPromise}async _init(t,{wipe:e,url:i,urlauto:r,fileDbName:n=t,fileStoreName:s=t+"_files",lockDbName:o=t+"_lock",lockStoreName:h=t+"_lock"}={}){await this._gracefulShutdown(),this._name=t,this._idb=new u(n,s),this._mutex=navigator.locks?new p(t):new _(o,h),this._cache=new a(t),this._opts={wipe:e,url:i},this._needsWipe=!!e,i&&(this._http=new d(i),this._urlauto=!!r),this._initPromiseResolve&&(this._initPromiseResolve(),this._initPromiseResolve=null),this.stat("/")}async _gracefulShutdown(){this._operations.size>0&&(this._isShuttingDown=!0,await new Promise(t=>this._gracefulShutdownResolve=t),this._isShuttingDown=!1,this._gracefulShutdownResolve=null)}_wrap(t,e){return async(...i)=>{let r={name:t.name,args:i};this._operations.add(r);try{return await this._activate(),await t.apply(this,i)}finally{this._operations.delete(r),e&&this.saveSuperblock(),0===this._operations.size&&(this._deactivationTimeout||clearTimeout(this._deactivationTimeout),this._deactivationTimeout=setTimeout(this._deactivate.bind(this),500))}}}async _activate(){if(this._initPromise||console.warn(new Error(`Attempted to use LightningFS ${this._name} before it was initialized.`)),await this._initPromise,this._deactivationTimeout&&(clearTimeout(this._deactivationTimeout),this._deactivationTimeout=null),this._deactivationPromise&&await this._deactivationPromise,this._deactivationPromise=null,this._activationPromise||(this._activationPromise=this.__activate()),await this._activationPromise,!await this._mutex.has())throw new l}async __activate(){if(this._cache.activated)return;this._needsWipe&&(this._needsWipe=!1,await this._idb.wipe(),await this._mutex.release({force:!0})),await this._mutex.has()||await this._mutex.wait();const t=await this._idb.loadSuperblock();if(t)this._cache.activate(t);else if(this._http){const t=await this._http.loadSuperblock();this._cache.activate(t),await this._saveSuperblock()}else this._cache.activate()}async _deactivate(){return this._activationPromise&&await this._activationPromise,this._deactivationPromise||(this._deactivationPromise=this.__deactivate()),this._activationPromise=null,this._gracefulShutdownResolve&&this._gracefulShutdownResolve(),this._deactivationPromise}async __deactivate(){await this._mutex.has()&&await this._saveSuperblock(),this._cache.deactivate();try{await this._mutex.release()}catch(t){console.log(t)}await this._idb.close()}async _saveSuperblock(){this._cache.activated&&(this._lastSavedAt=Date.now(),await this._idb.saveSuperblock(this._cache._root))}async _writeStat(t,e,i){let r=m.split(m.dirname(t)),n=r.shift();for(let t of r){n=m.join(n,t);try{this._cache.mkdir(n,{mode:511})}catch(t){}}return this._cache.writeStat(t,e,i)}async readFile(t,e){[t,e]=f(t,e);const{encoding:i}=e;if(i&&"utf8"!==i)throw new Error('Only "utf8" encoding is supported in readFile');let r=null,s=null;try{s=this._cache.stat(t),r=await this._idb.readFile(s.ino)}catch(t){if(!this._urlauto)throw t}if(!r&&this._http){let e=this._cache.lstat(t);for(;"symlink"===e.type;)t=m.resolve(m.dirname(t),e.target),e=this._cache.lstat(t);r=await this._http.readFile(t)}if(r&&(s&&s.size==r.byteLength||(s=await this._writeStat(t,r.byteLength,{mode:s?s.mode:438}),this.saveSuperblock()),"utf8"===i&&(r=n(r))),!s)throw new h(t);return r}async writeFile(t,e,i){[t,i]=f(t,i);const{mode:n,encoding:s="utf8"}=i;if("string"==typeof e){if("utf8"!==s)throw new Error('Only "utf8" encoding is supported in writeFile');e=r(e)}const o=await this._cache.writeStat(t,e.byteLength,{mode:n});return await this._idb.writeFile(o.ino,e),null}async unlink(t,e){[t,e]=f(t,e);const i=this._cache.lstat(t);return this._cache.unlink(t),"symlink"!==i.type&&await this._idb.unlink(i.ino),null}async readdir(t,e){return[t,e]=f(t,e),this._cache.readdir(t)}async mkdir(t,e){[t,e]=f(t,e);const{mode:i=511}=e;return await this._cache.mkdir(t,{mode:i}),null}async rmdir(t,e){if([t,e]=f(t,e),"/"===t)throw new c;return this._cache.rmdir(t),null}async rename(t,e){return[t,e]=w(t,e),this._cache.rename(t,e),null}async stat(t,e){[t,e]=f(t,e);const i=this._cache.stat(t);return new o(i)}async lstat(t,e){[t,e]=f(t,e);let i=this._cache.lstat(t);return new o(i)}async readlink(t,e){return[t,e]=f(t,e),this._cache.readlink(t)}async symlink(t,e){return[t,e]=w(t,e),this._cache.symlink(t,e),null}async backFile(t,e){[t,e]=f(t,e);let i=await this._http.sizeFile(t);return await this._writeStat(t,i,e),null}}},function(t,e,i){i(7),t.exports={encode:t=>(new TextEncoder).encode(t),decode:t=>(new TextDecoder).decode(t)}},function(t,e,i){(function(t){!function(t){function e(t){if("utf-8"!==(t=void 0===t?"utf-8":t))throw new RangeError("Failed to construct 'TextEncoder': The encoding label provided ('"+t+"') is invalid.")}function i(t,e){if(e=void 0===e?{fatal:!1}:e,"utf-8"!==(t=void 0===t?"utf-8":t))throw new RangeError("Failed to construct 'TextDecoder': The encoding label provided ('"+t+"') is invalid.");if(e.fatal)throw Error("Failed to construct 'TextDecoder': the 'fatal' option is unsupported.")}if(t.TextEncoder&&t.TextDecoder)return!1;Object.defineProperty(e.prototype,"encoding",{value:"utf-8"}),e.prototype.encode=function(t,e){if((e=void 0===e?{stream:!1}:e).stream)throw Error("Failed to encode: the 'stream' option is unsupported.");e=0;for(var i=t.length,r=0,n=Math.max(32,i+(i>>1)+7),s=new Uint8Array(n>>3<<3);e<i;){var o=t.charCodeAt(e++);if(55296<=o&&56319>=o){if(e<i){var a=t.charCodeAt(e);56320==(64512&a)&&(++e,o=((1023&o)<<10)+(1023&a)+65536)}if(55296<=o&&56319>=o)continue}if(r+4>s.length&&(n+=8,n=(n*=1+e/t.length*2)>>3<<3,(a=new Uint8Array(n)).set(s),s=a),0==(4294967168&o))s[r++]=o;else{if(0==(4294965248&o))s[r++]=o>>6&31|192;else if(0==(4294901760&o))s[r++]=o>>12&15|224,s[r++]=o>>6&63|128;else{if(0!=(4292870144&o))continue;s[r++]=o>>18&7|240,s[r++]=o>>12&63|128,s[r++]=o>>6&63|128}s[r++]=63&o|128}}return s.slice(0,r)},Object.defineProperty(i.prototype,"encoding",{value:"utf-8"}),Object.defineProperty(i.prototype,"fatal",{value:!1}),Object.defineProperty(i.prototype,"ignoreBOM",{value:!1}),i.prototype.decode=function(t,e){if((e=void 0===e?{stream:!1}:e).stream)throw Error("Failed to decode: the 'stream' option is unsupported.");e=0;for(var i=(t=new Uint8Array(t)).length,r=[];e<i;){var n=t[e++];if(0===n)break;if(0==(128&n))r.push(n);else if(192==(224&n)){var s=63&t[e++];r.push((31&n)<<6|s)}else if(224==(240&n)){s=63&t[e++];var o=63&t[e++];r.push((31&n)<<12|s<<6|o)}else if(240==(248&n)){65535<(n=(7&n)<<18|(s=63&t[e++])<<12|(o=63&t[e++])<<6|63&t[e++])&&(n-=65536,r.push(n>>>10&1023|55296),n=56320|1023&n),r.push(n)}}return String.fromCharCode.apply(null,r)},t.TextEncoder=e,t.TextDecoder=i}("undefined"!=typeof window?window:void 0!==t?t:this)}).call(this,i(8))},function(t,e){var i;i=function(){return this}();try{i=i||new Function("return this")()}catch(t){"object"==typeof window&&(i=window)}t.exports=i},function(t,e){t.exports=function(t,e,i){var r;return function(){if(!e)return t.apply(this,arguments);var n=this,s=arguments,o=i&&!r;return clearTimeout(r),r=setTimeout(function(){if(r=null,!o)return t.apply(n,s)},e),o?t.apply(this,arguments):void 0}}},function(t,e){t.exports=class{constructor(t){this.type=t.type,this.mode=t.mode,this.size=t.size,this.ino=t.ino,this.mtimeMs=t.mtimeMs,this.ctimeMs=t.ctimeMs||t.mtimeMs,this.uid=1,this.gid=1,this.dev=1}isFile(){return"file"===this.type}isDirectory(){return"dir"===this.type}isSymbolicLink(){return"symlink"===this.type}}},function(t,e,i){const r=i(0),{EEXIST:n,ENOENT:s,ENOTDIR:o,ENOTEMPTY:a}=i(1),h=0;t.exports=class{constructor(){}_makeRoot(t=new Map){return t.set(h,{mode:511,type:"dir",size:0,ino:0,mtimeMs:Date.now()}),t}activate(t=null){this._root=null===t?new Map([["/",this._makeRoot()]]):"string"==typeof t?new Map([["/",this._makeRoot(this.parse(t))]]):t}get activated(){return!!this._root}deactivate(){this._root=void 0}size(){return this._countInodes(this._root.get("/"))-1}_countInodes(t){let e=1;for(let[i,r]of t)i!==h&&(e+=this._countInodes(r));return e}autoinc(){return this._maxInode(this._root.get("/"))+1}_maxInode(t){let e=t.get(h).ino;for(let[i,r]of t)i!==h&&(e=Math.max(e,this._maxInode(r)));return e}print(t=this._root.get("/")){let e="";const i=(t,r)=>{for(let[n,s]of t){if(0===n)continue;let t=s.get(h),o=t.mode.toString(8);e+=`${"\t".repeat(r)}${n}\t${o}`,"file"===t.type?e+=`\t${t.size}\t${t.mtimeMs}\n`:(e+="\n",i(s,r+1))}};return i(t,0),e}parse(t){let e=0;function i(t){const i=++e,r=1===t.length?"dir":"file";let[n,s,o]=t;return n=parseInt(n,8),s=s?parseInt(s):0,o=o?parseInt(o):Date.now(),new Map([[h,{mode:n,type:r,size:s,mtimeMs:o,ino:i}]])}let r=t.trim().split("\n"),n=this._makeRoot(),s=[{indent:-1,node:n},{indent:0,node:null}];for(let t of r){let e=t.match(/^\t*/)[0].length;t=t.slice(e);let[r,...n]=t.split("\t"),o=i(n);if(e<=s[s.length-1].indent)for(;e<=s[s.length-1].indent;)s.pop();s.push({indent:e,node:o}),s[s.length-2].node.set(r,o)}return n}_lookup(t,e=!0){let i=this._root,n="/",o=r.split(t);for(let a=0;a<o.length;++a){let c=o[a];if(!(i=i.get(c)))throw new s(t);if(e||a<o.length-1){const t=i.get(h);if("symlink"===t.type){let e=r.resolve(n,t.target);i=this._lookup(e)}n=n?r.join(n,c):c}}return i}mkdir(t,{mode:e}){if("/"===t)throw new n;let i=this._lookup(r.dirname(t)),s=r.basename(t);if(i.has(s))throw new n;let o=new Map,a={mode:e,type:"dir",size:0,mtimeMs:Date.now(),ino:this.autoinc()};o.set(h,a),i.set(s,o)}rmdir(t){let e=this._lookup(t);if("dir"!==e.get(h).type)throw new o;if(e.size>1)throw new a;let i=this._lookup(r.dirname(t)),n=r.basename(t);i.delete(n)}readdir(t){let e=this._lookup(t);if("dir"!==e.get(h).type)throw new o;return[...e.keys()].filter(t=>"string"==typeof t)}writeStat(t,e,{mode:i}){let n;try{let e=this.stat(t);null==i&&(i=e.mode),n=e.ino}catch(t){}null==i&&(i=438),null==n&&(n=this.autoinc());let s=this._lookup(r.dirname(t)),o=r.basename(t),a={mode:i,type:"file",size:e,mtimeMs:Date.now(),ino:n},c=new Map;return c.set(h,a),s.set(o,c),a}unlink(t){let e=this._lookup(r.dirname(t)),i=r.basename(t);e.delete(i)}rename(t,e){let i=r.basename(e),n=this._lookup(t);this._lookup(r.dirname(e)).set(i,n),this.unlink(t)}stat(t){return this._lookup(t).get(h)}lstat(t){return this._lookup(t,!1).get(h)}readlink(t){return this._lookup(t,!1).get(h).target}symlink(t,e){let i,n;try{let t=this.stat(e);null===n&&(n=t.mode),i=t.ino}catch(t){}null==n&&(n=438),null==i&&(i=this.autoinc());let s=this._lookup(r.dirname(e)),o=r.basename(e),a={mode:n,type:"symlink",target:t,size:0,mtimeMs:Date.now(),ino:i},c=new Map;return c.set(h,a),s.set(o,c),a}}},function(t,e,i){const r=i(2);t.exports=class{constructor(t,e){this._database=t,this._storename=e,this._store=new r.Store(this._database,this._storename)}saveSuperblock(t){return r.set("!root",t,this._store)}loadSuperblock(){return r.get("!root",this._store)}readFile(t){return r.get(t,this._store)}writeFile(t,e){return r.set(t,e,this._store)}unlink(t){return r.del(t,this._store)}wipe(){return r.clear(this._store)}close(){return r.close(this._store)}}},function(t,e){t.exports=class{constructor(t){this._url=t}loadSuperblock(){return fetch(this._url+"/.superblock.txt").then(t=>t.ok?t.text():null)}async readFile(t){const e=await fetch(this._url+t);if(200===e.status)return e.arrayBuffer();throw new Error("ENOENT")}async sizeFile(t){const e=await fetch(this._url+t,{method:"HEAD"});if(200===e.status)return e.headers.get("content-length");throw new Error("ENOENT")}}},function(t,e,i){const r=i(2),n=t=>new Promise(e=>setTimeout(e,t));t.exports=class{constructor(t,e){this._id=Math.random(),this._database=t,this._storename=e,this._store=new r.Store(this._database,this._storename),this._lock=null}async has({margin:t=2e3}={}){if(this._lock&&this._lock.holder===this._id){const e=Date.now();return this._lock.expires>e+t||await this.renew()}return!1}async renew({ttl:t=5e3}={}){let e;return await r.update("lock",i=>{const r=Date.now()+t;return e=i&&i.holder===this._id,this._lock=e?{holder:this._id,expires:r}:i,this._lock},this._store),e}async acquire({ttl:t=5e3}={}){let e,i,n;if(await r.update("lock",r=>{const s=Date.now(),o=s+t;return i=r&&r.expires<s,e=void 0===r||i,n=r&&r.holder===this._id,this._lock=e?{holder:this._id,expires:o}:r,this._lock},this._store),n)throw new Error("Mutex double-locked");return e}async wait({interval:t=100,limit:e=6e3,ttl:i}={}){for(;e--;){if(await this.acquire({ttl:i}))return!0;await n(t)}throw new Error("Mutex timeout")}async release({force:t=!1}={}){let e,i,n;if(await r.update("lock",r=>(e=t||r&&r.holder===this._id,i=void 0===r,n=r&&r.holder!==this._id,this._lock=e?void 0:r,this._lock),this._store),await r.close(this._store),!e&&!t){if(i)throw new Error("Mutex double-freed");if(n)throw new Error("Mutex lost ownership")}return e}}},function(t,e){t.exports=class{constructor(t){this._id=Math.random(),this._database=t,this._has=!1,this._release=null}async has(){return this._has}async acquire(){return new Promise(t=>{navigator.locks.request(this._database+"_lock",{ifAvailable:!0},e=>(this._has=!!e,t(!!e),new Promise(t=>{this._release=t})))})}async wait({timeout:t=6e5}={}){return new Promise((e,i)=>{const r=new AbortController;setTimeout(()=>{r.abort(),i(new Error("Mutex timeout"))},t),navigator.locks.request(this._database+"_lock",{signal:r.signal},t=>(this._has=!!t,e(!!t),new Promise(t=>{this._release=t})))})}async release({force:t=!1}={}){this._has=!1,this._release?this._release():t&&navigator.locks.request(this._database+"_lock",{steal:!0},t=>!0)}}},function(t,e){const i="undefined"==typeof window?"worker":"main";t.exports=function(t){return performance.mark(`${t} start`),console.log(`${i}: ${t}`),console.time(`${i}: ${t}`),function(){performance.mark(`${t} end`),console.timeEnd(`${i}: ${t}`),performance.measure(`${t}`,`${t} start`,`${t} end`)}}}])});
\ No newline at end of file

A lib/localforage.min.js => lib/localforage.min.js +7 -0
@@ 0,0 1,7 @@
/*!
    localForage -- Offline Storage, Improved
    Version 1.7.3
    https://localforage.github.io/localForage
    (c) 2013-2017 Mozilla, Apache License 2.0
*/
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.localforage=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){(function(a){"use strict";function c(){k=!0;for(var a,b,c=l.length;c;){for(b=l,l=[],a=-1;++a<c;)b[a]();c=l.length}k=!1}function d(a){1!==l.push(a)||k||e()}var e,f=a.MutationObserver||a.WebKitMutationObserver;if(f){var g=0,h=new f(c),i=a.document.createTextNode("");h.observe(i,{characterData:!0}),e=function(){i.data=g=++g%2}}else if(a.setImmediate||void 0===a.MessageChannel)e="document"in a&&"onreadystatechange"in a.document.createElement("script")?function(){var b=a.document.createElement("script");b.onreadystatechange=function(){c(),b.onreadystatechange=null,b.parentNode.removeChild(b),b=null},a.document.documentElement.appendChild(b)}:function(){setTimeout(c,0)};else{var j=new a.MessageChannel;j.port1.onmessage=c,e=function(){j.port2.postMessage(0)}}var k,l=[];b.exports=d}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b,c){"use strict";function d(){}function e(a){if("function"!=typeof a)throw new TypeError("resolver must be a function");this.state=s,this.queue=[],this.outcome=void 0,a!==d&&i(this,a)}function f(a,b,c){this.promise=a,"function"==typeof b&&(this.onFulfilled=b,this.callFulfilled=this.otherCallFulfilled),"function"==typeof c&&(this.onRejected=c,this.callRejected=this.otherCallRejected)}function g(a,b,c){o(function(){var d;try{d=b(c)}catch(b){return p.reject(a,b)}d===a?p.reject(a,new TypeError("Cannot resolve promise with itself")):p.resolve(a,d)})}function h(a){var b=a&&a.then;if(a&&("object"==typeof a||"function"==typeof a)&&"function"==typeof b)return function(){b.apply(a,arguments)}}function i(a,b){function c(b){f||(f=!0,p.reject(a,b))}function d(b){f||(f=!0,p.resolve(a,b))}function e(){b(d,c)}var f=!1,g=j(e);"error"===g.status&&c(g.value)}function j(a,b){var c={};try{c.value=a(b),c.status="success"}catch(a){c.status="error",c.value=a}return c}function k(a){return a instanceof this?a:p.resolve(new this(d),a)}function l(a){var b=new this(d);return p.reject(b,a)}function m(a){function b(a,b){function d(a){g[b]=a,++h!==e||f||(f=!0,p.resolve(j,g))}c.resolve(a).then(d,function(a){f||(f=!0,p.reject(j,a))})}var c=this;if("[object Array]"!==Object.prototype.toString.call(a))return this.reject(new TypeError("must be an array"));var e=a.length,f=!1;if(!e)return this.resolve([]);for(var g=new Array(e),h=0,i=-1,j=new this(d);++i<e;)b(a[i],i);return j}function n(a){function b(a){c.resolve(a).then(function(a){f||(f=!0,p.resolve(h,a))},function(a){f||(f=!0,p.reject(h,a))})}var c=this;if("[object Array]"!==Object.prototype.toString.call(a))return this.reject(new TypeError("must be an array"));var e=a.length,f=!1;if(!e)return this.resolve([]);for(var g=-1,h=new this(d);++g<e;)b(a[g]);return h}var o=a(1),p={},q=["REJECTED"],r=["FULFILLED"],s=["PENDING"];b.exports=e,e.prototype.catch=function(a){return this.then(null,a)},e.prototype.then=function(a,b){if("function"!=typeof a&&this.state===r||"function"!=typeof b&&this.state===q)return this;var c=new this.constructor(d);if(this.state!==s){g(c,this.state===r?a:b,this.outcome)}else this.queue.push(new f(c,a,b));return c},f.prototype.callFulfilled=function(a){p.resolve(this.promise,a)},f.prototype.otherCallFulfilled=function(a){g(this.promise,this.onFulfilled,a)},f.prototype.callRejected=function(a){p.reject(this.promise,a)},f.prototype.otherCallRejected=function(a){g(this.promise,this.onRejected,a)},p.resolve=function(a,b){var c=j(h,b);if("error"===c.status)return p.reject(a,c.value);var d=c.value;if(d)i(a,d);else{a.state=r,a.outcome=b;for(var e=-1,f=a.queue.length;++e<f;)a.queue[e].callFulfilled(b)}return a},p.reject=function(a,b){a.state=q,a.outcome=b;for(var c=-1,d=a.queue.length;++c<d;)a.queue[c].callRejected(b);return a},e.resolve=k,e.reject=l,e.all=m,e.race=n},{1:1}],3:[function(a,b,c){(function(b){"use strict";"function"!=typeof b.Promise&&(b.Promise=a(2))}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{2:2}],4:[function(a,b,c){"use strict";function d(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function e(){try{if("undefined"!=typeof indexedDB)return indexedDB;if("undefined"!=typeof webkitIndexedDB)return webkitIndexedDB;if("undefined"!=typeof mozIndexedDB)return mozIndexedDB;if("undefined"!=typeof OIndexedDB)return OIndexedDB;if("undefined"!=typeof msIndexedDB)return msIndexedDB}catch(a){return}}function f(){try{if(!ua)return!1;var a="undefined"!=typeof openDatabase&&/(Safari|iPhone|iPad|iPod)/.test(navigator.userAgent)&&!/Chrome/.test(navigator.userAgent)&&!/BlackBerry/.test(navigator.platform),b="function"==typeof fetch&&-1!==fetch.toString().indexOf("[native code");return(!a||b)&&"undefined"!=typeof indexedDB&&"undefined"!=typeof IDBKeyRange}catch(a){return!1}}function g(a,b){a=a||[],b=b||{};try{return new Blob(a,b)}catch(f){if("TypeError"!==f.name)throw f;for(var c="undefined"!=typeof BlobBuilder?BlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder?MozBlobBuilder:WebKitBlobBuilder,d=new c,e=0;e<a.length;e+=1)d.append(a[e]);return d.getBlob(b.type)}}function h(a,b){b&&a.then(function(a){b(null,a)},function(a){b(a)})}function i(a,b,c){"function"==typeof b&&a.then(b),"function"==typeof c&&a.catch(c)}function j(a){return"string"!=typeof a&&(console.warn(a+" used as a key, but it is not a string."),a=String(a)),a}function k(){if(arguments.length&&"function"==typeof arguments[arguments.length-1])return arguments[arguments.length-1]}function l(a){for(var b=a.length,c=new ArrayBuffer(b),d=new Uint8Array(c),e=0;e<b;e++)d[e]=a.charCodeAt(e);return c}function m(a){return new va(function(b){var c=a.transaction(wa,Ba),d=g([""]);c.objectStore(wa).put(d,"key"),c.onabort=function(a){a.preventDefault(),a.stopPropagation(),b(!1)},c.oncomplete=function(){var a=navigator.userAgent.match(/Chrome\/(\d+)/),c=navigator.userAgent.match(/Edge\//);b(c||!a||parseInt(a[1],10)>=43)}}).catch(function(){return!1})}function n(a){return"boolean"==typeof xa?va.resolve(xa):m(a).then(function(a){return xa=a})}function o(a){var b=ya[a.name],c={};c.promise=new va(function(a,b){c.resolve=a,c.reject=b}),b.deferredOperations.push(c),b.dbReady?b.dbReady=b.dbReady.then(function(){return c.promise}):b.dbReady=c.promise}function p(a){var b=ya[a.name],c=b.deferredOperations.pop();if(c)return c.resolve(),c.promise}function q(a,b){var c=ya[a.name],d=c.deferredOperations.pop();if(d)return d.reject(b),d.promise}function r(a,b){return new va(function(c,d){if(ya[a.name]=ya[a.name]||B(),a.db){if(!b)return c(a.db);o(a),a.db.close()}var e=[a.name];b&&e.push(a.version);var f=ua.open.apply(ua,e);b&&(f.onupgradeneeded=function(b){var c=f.result;try{c.createObjectStore(a.storeName),b.oldVersion<=1&&c.createObjectStore(wa)}catch(c){if("ConstraintError"!==c.name)throw c;console.warn('The database "'+a.name+'" has been upgraded from version '+b.oldVersion+" to version "+b.newVersion+', but the storage "'+a.storeName+'" already exists.')}}),f.onerror=function(a){a.preventDefault(),d(f.error)},f.onsuccess=function(){c(f.result),p(a)}})}function s(a){return r(a,!1)}function t(a){return r(a,!0)}function u(a,b){if(!a.db)return!0;var c=!a.db.objectStoreNames.contains(a.storeName),d=a.version<a.db.version,e=a.version>a.db.version;if(d&&(a.version!==b&&console.warn('The database "'+a.name+"\" can't be downgraded from version "+a.db.version+" to version "+a.version+"."),a.version=a.db.version),e||c){if(c){var f=a.db.version+1;f>a.version&&(a.version=f)}return!0}return!1}function v(a){return new va(function(b,c){var d=new FileReader;d.onerror=c,d.onloadend=function(c){var d=btoa(c.target.result||"");b({__local_forage_encoded_blob:!0,data:d,type:a.type})},d.readAsBinaryString(a)})}function w(a){return g([l(atob(a.data))],{type:a.type})}function x(a){return a&&a.__local_forage_encoded_blob}function y(a){var b=this,c=b._initReady().then(function(){var a=ya[b._dbInfo.name];if(a&&a.dbReady)return a.dbReady});return i(c,a,a),c}function z(a){o(a);for(var b=ya[a.name],c=b.forages,d=0;d<c.length;d++){var e=c[d];e._dbInfo.db&&(e._dbInfo.db.close(),e._dbInfo.db=null)}return a.db=null,s(a).then(function(b){return a.db=b,u(a)?t(a):b}).then(function(d){a.db=b.db=d;for(var e=0;e<c.length;e++)c[e]._dbInfo.db=d}).catch(function(b){throw q(a,b),b})}function A(a,b,c,d){void 0===d&&(d=1);try{var e=a.db.transaction(a.storeName,b);c(null,e)}catch(e){if(d>0&&(!a.db||"InvalidStateError"===e.name||"NotFoundError"===e.name))return va.resolve().then(function(){if(!a.db||"NotFoundError"===e.name&&!a.db.objectStoreNames.contains(a.storeName)&&a.version<=a.db.version)return a.db&&(a.version=a.db.version+1),t(a)}).then(function(){return z(a).then(function(){A(a,b,c,d-1)})}).catch(c);c(e)}}function B(){return{forages:[],db:null,dbReady:null,deferredOperations:[]}}function C(a){function b(){return va.resolve()}var c=this,d={db:null};if(a)for(var e in a)d[e]=a[e];var f=ya[d.name];f||(f=B(),ya[d.name]=f),f.forages.push(c),c._initReady||(c._initReady=c.ready,c.ready=y);for(var g=[],h=0;h<f.forages.length;h++){var i=f.forages[h];i!==c&&g.push(i._initReady().catch(b))}var j=f.forages.slice(0);return va.all(g).then(function(){return d.db=f.db,s(d)}).then(function(a){return d.db=a,u(d,c._defaultConfig.version)?t(d):a}).then(function(a){d.db=f.db=a,c._dbInfo=d;for(var b=0;b<j.length;b++){var e=j[b];e!==c&&(e._dbInfo.db=d.db,e._dbInfo.version=d.version)}})}function D(a,b){var c=this;a=j(a);var d=new va(function(b,d){c.ready().then(function(){A(c._dbInfo,Aa,function(e,f){if(e)return d(e);try{var g=f.objectStore(c._dbInfo.storeName),h=g.get(a);h.onsuccess=function(){var a=h.result;void 0===a&&(a=null),x(a)&&(a=w(a)),b(a)},h.onerror=function(){d(h.error)}}catch(a){d(a)}})}).catch(d)});return h(d,b),d}function E(a,b){var c=this,d=new va(function(b,d){c.ready().then(function(){A(c._dbInfo,Aa,function(e,f){if(e)return d(e);try{var g=f.objectStore(c._dbInfo.storeName),h=g.openCursor(),i=1;h.onsuccess=function(){var c=h.result;if(c){var d=c.value;x(d)&&(d=w(d));var e=a(d,c.key,i++);void 0!==e?b(e):c.continue()}else b()},h.onerror=function(){d(h.error)}}catch(a){d(a)}})}).catch(d)});return h(d,b),d}function F(a,b,c){var d=this;a=j(a);var e=new va(function(c,e){var f;d.ready().then(function(){return f=d._dbInfo,"[object Blob]"===za.call(b)?n(f.db).then(function(a){return a?b:v(b)}):b}).then(function(b){A(d._dbInfo,Ba,function(f,g){if(f)return e(f);try{var h=g.objectStore(d._dbInfo.storeName);null===b&&(b=void 0);var i=h.put(b,a);g.oncomplete=function(){void 0===b&&(b=null),c(b)},g.onabort=g.onerror=function(){var a=i.error?i.error:i.transaction.error;e(a)}}catch(a){e(a)}})}).catch(e)});return h(e,c),e}function G(a,b){var c=this;a=j(a);var d=new va(function(b,d){c.ready().then(function(){A(c._dbInfo,Ba,function(e,f){if(e)return d(e);try{var g=f.objectStore(c._dbInfo.storeName),h=g.delete(a);f.oncomplete=function(){b()},f.onerror=function(){d(h.error)},f.onabort=function(){var a=h.error?h.error:h.transaction.error;d(a)}}catch(a){d(a)}})}).catch(d)});return h(d,b),d}function H(a){var b=this,c=new va(function(a,c){b.ready().then(function(){A(b._dbInfo,Ba,function(d,e){if(d)return c(d);try{var f=e.objectStore(b._dbInfo.storeName),g=f.clear();e.oncomplete=function(){a()},e.onabort=e.onerror=function(){var a=g.error?g.error:g.transaction.error;c(a)}}catch(a){c(a)}})}).catch(c)});return h(c,a),c}function I(a){var b=this,c=new va(function(a,c){b.ready().then(function(){A(b._dbInfo,Aa,function(d,e){if(d)return c(d);try{var f=e.objectStore(b._dbInfo.storeName),g=f.count();g.onsuccess=function(){a(g.result)},g.onerror=function(){c(g.error)}}catch(a){c(a)}})}).catch(c)});return h(c,a),c}function J(a,b){var c=this,d=new va(function(b,d){if(a<0)return void b(null);c.ready().then(function(){A(c._dbInfo,Aa,function(e,f){if(e)return d(e);try{var g=f.objectStore(c._dbInfo.storeName),h=!1,i=g.openCursor();i.onsuccess=function(){var c=i.result;if(!c)return void b(null);0===a?b(c.key):h?b(c.key):(h=!0,c.advance(a))},i.onerror=function(){d(i.error)}}catch(a){d(a)}})}).catch(d)});return h(d,b),d}function K(a){var b=this,c=new va(function(a,c){b.ready().then(function(){A(b._dbInfo,Aa,function(d,e){if(d)return c(d);try{var f=e.objectStore(b._dbInfo.storeName),g=f.openCursor(),h=[];g.onsuccess=function(){var b=g.result;if(!b)return void a(h);h.push(b.key),b.continue()},g.onerror=function(){c(g.error)}}catch(a){c(a)}})}).catch(c)});return h(c,a),c}function L(a,b){b=k.apply(this,arguments);var c=this.config();a="function"!=typeof a&&a||{},a.name||(a.name=a.name||c.name,a.storeName=a.storeName||c.storeName);var d,e=this;if(a.name){var f=a.name===c.name&&e._dbInfo.db,g=f?va.resolve(e._dbInfo.db):s(a).then(function(b){var c=ya[a.name],d=c.forages;c.db=b;for(var e=0;e<d.length;e++)d[e]._dbInfo.db=b;return b});d=a.storeName?g.then(function(b){if(b.objectStoreNames.contains(a.storeName)){var c=b.version+1;o(a);var d=ya[a.name],e=d.forages;b.close();for(var f=0;f<e.length;f++){var g=e[f];g._dbInfo.db=null,g._dbInfo.version=c}return new va(function(b,d){var e=ua.open(a.name,c);e.onerror=function(a){e.result.close(),d(a)},e.onupgradeneeded=function(){e.result.deleteObjectStore(a.storeName)},e.onsuccess=function(){var a=e.result;a.close(),b(a)}}).then(function(a){d.db=a;for(var b=0;b<e.length;b++){var c=e[b];c._dbInfo.db=a,p(c._dbInfo)}}).catch(function(b){throw(q(a,b)||va.resolve()).catch(function(){}),b})}}):g.then(function(b){o(a);var c=ya[a.name],d=c.forages;b.close();for(var e=0;e<d.length;e++){d[e]._dbInfo.db=null}return new va(function(b,c){var d=ua.deleteDatabase(a.name);d.onerror=d.onblocked=function(a){var b=d.result;b&&b.close(),c(a)},d.onsuccess=function(){var a=d.result;a&&a.close(),b(a)}}).then(function(a){c.db=a;for(var b=0;b<d.length;b++)p(d[b]._dbInfo)}).catch(function(b){throw(q(a,b)||va.resolve()).catch(function(){}),b})})}else d=va.reject("Invalid arguments");return h(d,b),d}function M(){return"function"==typeof openDatabase}function N(a){var b,c,d,e,f,g=.75*a.length,h=a.length,i=0;"="===a[a.length-1]&&(g--,"="===a[a.length-2]&&g--);var j=new ArrayBuffer(g),k=new Uint8Array(j);for(b=0;b<h;b+=4)c=Da.indexOf(a[b]),d=Da.indexOf(a[b+1]),e=Da.indexOf(a[b+2]),f=Da.indexOf(a[b+3]),k[i++]=c<<2|d>>4,k[i++]=(15&d)<<4|e>>2,k[i++]=(3&e)<<6|63&f;return j}function O(a){var b,c=new Uint8Array(a),d="";for(b=0;b<c.length;b+=3)d+=Da[c[b]>>2],d+=Da[(3&c[b])<<4|c[b+1]>>4],d+=Da[(15&c[b+1])<<2|c[b+2]>>6],d+=Da[63&c[b+2]];return c.length%3==2?d=d.substring(0,d.length-1)+"=":c.length%3==1&&(d=d.substring(0,d.length-2)+"=="),d}function P(a,b){var c="";if(a&&(c=Ua.call(a)),a&&("[object ArrayBuffer]"===c||a.buffer&&"[object ArrayBuffer]"===Ua.call(a.buffer))){var d,e=Ga;a instanceof ArrayBuffer?(d=a,e+=Ia):(d=a.buffer,"[object Int8Array]"===c?e+=Ka:"[object Uint8Array]"===c?e+=La:"[object Uint8ClampedArray]"===c?e+=Ma:"[object Int16Array]"===c?e+=Na:"[object Uint16Array]"===c?e+=Pa:"[object Int32Array]"===c?e+=Oa:"[object Uint32Array]"===c?e+=Qa:"[object Float32Array]"===c?e+=Ra:"[object Float64Array]"===c?e+=Sa:b(new Error("Failed to get type for BinaryArray"))),b(e+O(d))}else if("[object Blob]"===c){var f=new FileReader;f.onload=function(){var c=Ea+a.type+"~"+O(this.result);b(Ga+Ja+c)},f.readAsArrayBuffer(a)}else try{b(JSON.stringify(a))}catch(c){console.error("Couldn't convert value into a JSON string: ",a),b(null,c)}}function Q(a){if(a.substring(0,Ha)!==Ga)return JSON.parse(a);var b,c=a.substring(Ta),d=a.substring(Ha,Ta);if(d===Ja&&Fa.test(c)){var e=c.match(Fa);b=e[1],c=c.substring(e[0].length)}var f=N(c);switch(d){case Ia:return f;case Ja:return g([f],{type:b});case Ka:return new Int8Array(f);case La:return new Uint8Array(f);case Ma:return new Uint8ClampedArray(f);case Na:return new Int16Array(f);case Pa:return new Uint16Array(f);case Oa:return new Int32Array(f);case Qa:return new Uint32Array(f);case Ra:return new Float32Array(f);case Sa:return new Float64Array(f);default:throw new Error("Unkown type: "+d)}}function R(a,b,c,d){a.executeSql("CREATE TABLE IF NOT EXISTS "+b.storeName+" (id INTEGER PRIMARY KEY, key unique, value)",[],c,d)}function S(a){var b=this,c={db:null};if(a)for(var d in a)c[d]="string"!=typeof a[d]?a[d].toString():a[d];var e=new va(function(a,d){try{c.db=openDatabase(c.name,String(c.version),c.description,c.size)}catch(a){return d(a)}c.db.transaction(function(e){R(e,c,function(){b._dbInfo=c,a()},function(a,b){d(b)})},d)});return c.serializer=Va,e}function T(a,b,c,d,e,f){a.executeSql(c,d,e,function(a,g){g.code===g.SYNTAX_ERR?a.executeSql("SELECT name FROM sqlite_master WHERE type='table' AND name = ?",[b.storeName],function(a,h){h.rows.length?f(a,g):R(a,b,function(){a.executeSql(c,d,e,f)},f)},f):f(a,g)},f)}function U(a,b){var c=this;a=j(a);var d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"SELECT * FROM "+e.storeName+" WHERE key = ? LIMIT 1",[a],function(a,c){var d=c.rows.length?c.rows.item(0).value:null;d&&(d=e.serializer.deserialize(d)),b(d)},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function V(a,b){var c=this,d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"SELECT * FROM "+e.storeName,[],function(c,d){for(var f=d.rows,g=f.length,h=0;h<g;h++){var i=f.item(h),j=i.value;if(j&&(j=e.serializer.deserialize(j)),void 0!==(j=a(j,i.key,h+1)))return void b(j)}b()},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function W(a,b,c,d){var e=this;a=j(a);var f=new va(function(f,g){e.ready().then(function(){void 0===b&&(b=null);var h=b,i=e._dbInfo;i.serializer.serialize(b,function(b,j){j?g(j):i.db.transaction(function(c){T(c,i,"INSERT OR REPLACE INTO "+i.storeName+" (key, value) VALUES (?, ?)",[a,b],function(){f(h)},function(a,b){g(b)})},function(b){if(b.code===b.QUOTA_ERR){if(d>0)return void f(W.apply(e,[a,h,c,d-1]));g(b)}})})}).catch(g)});return h(f,c),f}function X(a,b,c){return W.apply(this,[a,b,c,1])}function Y(a,b){var c=this;a=j(a);var d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"DELETE FROM "+e.storeName+" WHERE key = ?",[a],function(){b()},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function Z(a){var b=this,c=new va(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){T(b,d,"DELETE FROM "+d.storeName,[],function(){a()},function(a,b){c(b)})})}).catch(c)});return h(c,a),c}function $(a){var b=this,c=new va(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){T(b,d,"SELECT COUNT(key) as c FROM "+d.storeName,[],function(b,c){var d=c.rows.item(0).c;a(d)},function(a,b){c(b)})})}).catch(c)});return h(c,a),c}function _(a,b){var c=this,d=new va(function(b,d){c.ready().then(function(){var e=c._dbInfo;e.db.transaction(function(c){T(c,e,"SELECT key FROM "+e.storeName+" WHERE id = ? LIMIT 1",[a+1],function(a,c){var d=c.rows.length?c.rows.item(0).key:null;b(d)},function(a,b){d(b)})})}).catch(d)});return h(d,b),d}function aa(a){var b=this,c=new va(function(a,c){b.ready().then(function(){var d=b._dbInfo;d.db.transaction(function(b){T(b,d,"SELECT key FROM "+d.storeName,[],function(b,c){for(var d=[],e=0;e<c.rows.length;e++)d.push(c.rows.item(e).key);a(d)},function(a,b){c(b)})})}).catch(c)});return h(c,a),c}function ba(a){return new va(function(b,c){a.transaction(function(d){d.executeSql("SELECT name FROM sqlite_master WHERE type='table' AND name <> '__WebKitDatabaseInfoTable__'",[],function(c,d){for(var e=[],f=0;f<d.rows.length;f++)e.push(d.rows.item(f).name);b({db:a,storeNames:e})},function(a,b){c(b)})},function(a){c(a)})})}function ca(a,b){b=k.apply(this,arguments);var c=this.config();a="function"!=typeof a&&a||{},a.name||(a.name=a.name||c.name,a.storeName=a.storeName||c.storeName);var d,e=this;return d=a.name?new va(function(b){var d;d=a.name===c.name?e._dbInfo.db:openDatabase(a.name,"","",0),b(a.storeName?{db:d,storeNames:[a.storeName]}:ba(d))}).then(function(a){return new va(function(b,c){a.db.transaction(function(d){function e(a){return new va(function(b,c){d.executeSql("DROP TABLE IF EXISTS "+a,[],function(){b()},function(a,b){c(b)})})}for(var f=[],g=0,h=a.storeNames.length;g<h;g++)f.push(e(a.storeNames[g]));va.all(f).then(function(){b()}).catch(function(a){c(a)})},function(a){c(a)})})}):va.reject("Invalid arguments"),h(d,b),d}function da(){try{return"undefined"!=typeof localStorage&&"setItem"in localStorage&&!!localStorage.setItem}catch(a){return!1}}function ea(a,b){var c=a.name+"/";return a.storeName!==b.storeName&&(c+=a.storeName+"/"),c}function fa(){var a="_localforage_support_test";try{return localStorage.setItem(a,!0),localStorage.removeItem(a),!1}catch(a){return!0}}function ga(){return!fa()||localStorage.length>0}function ha(a){var b=this,c={};if(a)for(var d in a)c[d]=a[d];return c.keyPrefix=ea(a,b._defaultConfig),ga()?(b._dbInfo=c,c.serializer=Va,va.resolve()):va.reject()}function ia(a){var b=this,c=b.ready().then(function(){for(var a=b._dbInfo.keyPrefix,c=localStorage.length-1;c>=0;c--){var d=localStorage.key(c);0===d.indexOf(a)&&localStorage.removeItem(d)}});return h(c,a),c}function ja(a,b){var c=this;a=j(a);var d=c.ready().then(function(){var b=c._dbInfo,d=localStorage.getItem(b.keyPrefix+a);return d&&(d=b.serializer.deserialize(d)),d});return h(d,b),d}function ka(a,b){var c=this,d=c.ready().then(function(){for(var b=c._dbInfo,d=b.keyPrefix,e=d.length,f=localStorage.length,g=1,h=0;h<f;h++){var i=localStorage.key(h);if(0===i.indexOf(d)){var j=localStorage.getItem(i);if(j&&(j=b.serializer.deserialize(j)),void 0!==(j=a(j,i.substring(e),g++)))return j}}});return h(d,b),d}function la(a,b){var c=this,d=c.ready().then(function(){var b,d=c._dbInfo;try{b=localStorage.key(a)}catch(a){b=null}return b&&(b=b.substring(d.keyPrefix.length)),b});return h(d,b),d}function ma(a){var b=this,c=b.ready().then(function(){for(var a=b._dbInfo,c=localStorage.length,d=[],e=0;e<c;e++){var f=localStorage.key(e);0===f.indexOf(a.keyPrefix)&&d.push(f.substring(a.keyPrefix.length))}return d});return h(c,a),c}function na(a){var b=this,c=b.keys().then(function(a){return a.length});return h(c,a),c}function oa(a,b){var c=this;a=j(a);var d=c.ready().then(function(){var b=c._dbInfo;localStorage.removeItem(b.keyPrefix+a)});return h(d,b),d}function pa(a,b,c){var d=this;a=j(a);var e=d.ready().then(function(){void 0===b&&(b=null);var c=b;return new va(function(e,f){var g=d._dbInfo;g.serializer.serialize(b,function(b,d){if(d)f(d);else try{localStorage.setItem(g.keyPrefix+a,b),e(c)}catch(a){"QuotaExceededError"!==a.name&&"NS_ERROR_DOM_QUOTA_REACHED"!==a.name||f(a),f(a)}})})});return h(e,c),e}function qa(a,b){if(b=k.apply(this,arguments),a="function"!=typeof a&&a||{},!a.name){var c=this.config();a.name=a.name||c.name,a.storeName=a.storeName||c.storeName}var d,e=this;return d=a.name?new va(function(b){b(a.storeName?ea(a,e._defaultConfig):a.name+"/")}).then(function(a){for(var b=localStorage.length-1;b>=0;b--){var c=localStorage.key(b);0===c.indexOf(a)&&localStorage.removeItem(c)}}):va.reject("Invalid arguments"),h(d,b),d}function ra(a,b){a[b]=function(){var c=arguments;return a.ready().then(function(){return a[b].apply(a,c)})}}function sa(){for(var a=1;a<arguments.length;a++){var b=arguments[a];if(b)for(var c in b)b.hasOwnProperty(c)&&($a(b[c])?arguments[0][c]=b[c].slice():arguments[0][c]=b[c])}return arguments[0]}var ta="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},ua=e();"undefined"==typeof Promise&&a(3);var va=Promise,wa="local-forage-detect-blob-support",xa=void 0,ya={},za=Object.prototype.toString,Aa="readonly",Ba="readwrite",Ca={_driver:"asyncStorage",_initStorage:C,_support:f(),iterate:E,getItem:D,setItem:F,removeItem:G,clear:H,length:I,key:J,keys:K,dropInstance:L},Da="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",Ea="~~local_forage_type~",Fa=/^~~local_forage_type~([^~]+)~/,Ga="__lfsc__:",Ha=Ga.length,Ia="arbf",Ja="blob",Ka="si08",La="ui08",Ma="uic8",Na="si16",Oa="si32",Pa="ur16",Qa="ui32",Ra="fl32",Sa="fl64",Ta=Ha+Ia.length,Ua=Object.prototype.toString,Va={serialize:P,deserialize:Q,stringToBuffer:N,bufferToString:O},Wa={_driver:"webSQLStorage",_initStorage:S,_support:M(),iterate:V,getItem:U,setItem:X,removeItem:Y,clear:Z,length:$,key:_,keys:aa,dropInstance:ca},Xa={_driver:"localStorageWrapper",_initStorage:ha,_support:da(),iterate:ka,getItem:ja,setItem:pa,removeItem:oa,clear:ia,length:na,key:la,keys:ma,dropInstance:qa},Ya=function(a,b){return a===b||"number"==typeof a&&"number"==typeof b&&isNaN(a)&&isNaN(b)},Za=function(a,b){for(var c=a.length,d=0;d<c;){if(Ya(a[d],b))return!0;d++}return!1},$a=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},_a={},ab={},bb={INDEXEDDB:Ca,WEBSQL:Wa,LOCALSTORAGE:Xa},cb=[bb.INDEXEDDB._driver,bb.WEBSQL._driver,bb.LOCALSTORAGE._driver],db=["dropInstance"],eb=["clear","getItem","iterate","key","keys","length","removeItem","setItem"].concat(db),fb={description:"",driver:cb.slice(),name:"localforage",size:4980736,storeName:"keyvaluepairs",version:1},gb=function(){function a(b){d(this,a);for(var c in bb)if(bb.hasOwnProperty(c)){var e=bb[c],f=e._driver;this[c]=f,_a[f]||this.defineDriver(e)}this._defaultConfig=sa({},fb),this._config=sa({},this._defaultConfig,b),this._driverSet=null,this._initDriver=null,this._ready=!1,this._dbInfo=null,this._wrapLibraryMethodsWithReady(),this.setDriver(this._config.driver).catch(function(){})}return a.prototype.config=function(a){if("object"===(void 0===a?"undefined":ta(a))){if(this._ready)return new Error("Can't call config() after localforage has been used.");for(var b in a){if("storeName"===b&&(a[b]=a[b].replace(/\W/g,"_")),"version"===b&&"number"!=typeof a[b])return new Error("Database version must be a number.");this._config[b]=a[b]}return!("driver"in a&&a.driver)||this.setDriver(this._config.driver)}return"string"==typeof a?this._config[a]:this._config},a.prototype.defineDriver=function(a,b,c){var d=new va(function(b,c){try{var d=a._driver,e=new Error("Custom driver not compliant; see https://mozilla.github.io/localForage/#definedriver");if(!a._driver)return void c(e);for(var f=eb.concat("_initStorage"),g=0,i=f.length;g<i;g++){var j=f[g];if((!Za(db,j)||a[j])&&"function"!=typeof a[j])return void c(e)}(function(){for(var b=function(a){return function(){var b=new Error("Method "+a+" is not implemented by the current driver"),c=va.reject(b);return h(c,arguments[arguments.length-1]),c}},c=0,d=db.length;c<d;c++){var e=db[c];a[e]||(a[e]=b(e))}})();var k=function(c){_a[d]&&console.info("Redefining LocalForage driver: "+d),_a[d]=a,ab[d]=c,b()};"_support"in a?a._support&&"function"==typeof a._support?a._support().then(k,c):k(!!a._support):k(!0)}catch(a){c(a)}});return i(d,b,c),d},a.prototype.driver=function(){return this._driver||null},a.prototype.getDriver=function(a,b,c){var d=_a[a]?va.resolve(_a[a]):va.reject(new Error("Driver not found."));return i(d,b,c),d},a.prototype.getSerializer=function(a){var b=va.resolve(Va);return i(b,a),b},a.prototype.ready=function(a){var b=this,c=b._driverSet.then(function(){return null===b._ready&&(b._ready=b._initDriver()),b._ready});return i(c,a,a),c},a.prototype.setDriver=function(a,b,c){function d(){g._config.driver=g.driver()}function e(a){return g._extend(a),d(),g._ready=g._initStorage(g._config),g._ready}function f(a){return function(){function b(){for(;c<a.length;){var f=a[c];return c++,g._dbInfo=null,g._ready=null,g.getDriver(f).then(e).catch(b)}d();var h=new Error("No available storage method found.");return g._driverSet=va.reject(h),g._driverSet}var c=0;return b()}}var g=this;$a(a)||(a=[a]);var h=this._getSupportedDrivers(a),j=null!==this._driverSet?this._driverSet.catch(function(){return va.resolve()}):va.resolve();return this._driverSet=j.then(function(){var a=h[0];return g._dbInfo=null,g._ready=null,g.getDriver(a).then(function(a){g._driver=a._driver,d(),g._wrapLibraryMethodsWithReady(),g._initDriver=f(h)})}).catch(function(){d();var a=new Error("No available storage method found.");return g._driverSet=va.reject(a),g._driverSet}),i(this._driverSet,b,c),this._driverSet},a.prototype.supports=function(a){return!!ab[a]},a.prototype._extend=function(a){sa(this,a)},a.prototype._getSupportedDrivers=function(a){for(var b=[],c=0,d=a.length;c<d;c++){var e=a[c];this.supports(e)&&b.push(e)}return b},a.prototype._wrapLibraryMethodsWithReady=function(){for(var a=0,b=eb.length;a<b;a++)ra(this,eb[a])},a.prototype.createInstance=function(b){return new a(b)},a}(),hb=new gb;b.exports=hb},{3:3}]},{},[4])(4)});
\ No newline at end of file

M package-lock.json => package-lock.json +1 -296
@@ 1,6 1,6 @@
{
  "name": "bogbook",
  "version": "1.8.2",
  "version": "1.8.1",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {


@@ 18,65 18,11 @@
      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
    },
    "array-flatten": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
    },
    "async-limiter": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
    },
    "body-parser": {
      "version": "1.19.0",
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
      "requires": {
        "bytes": "3.1.0",
        "content-type": "~1.0.4",
        "debug": "2.6.9",
        "depd": "~1.1.2",
        "http-errors": "1.7.2",
        "iconv-lite": "0.4.24",
        "on-finished": "~2.3.0",
        "qs": "6.7.0",
        "raw-body": "2.4.0",
        "type-is": "~1.6.17"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          }
        },
        "http-errors": {
          "version": "1.7.2",
          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
          "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
          "requires": {
            "depd": "~1.1.2",
            "inherits": "2.0.3",
            "setprototypeof": "1.1.1",
            "statuses": ">= 1.5.0 < 2",
            "toidentifier": "1.0.0"
          }
        },
        "inherits": {
          "version": "2.0.3",
          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
        }
      }
    },
    "bytes": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
    },
    "cache-content-type": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",


@@ 104,16 50,6 @@
      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
    },
    "cookie": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
    },
    "cookie-signature": {
      "version": "1.0.6",
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
    },
    "cookies": {
      "version": "0.8.0",
      "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz",


@@ 193,87 129,6 @@
      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
    },
    "etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
    },
    "express": {
      "version": "4.17.1",
      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
      "requires": {
        "accepts": "~1.3.7",
        "array-flatten": "1.1.1",
        "body-parser": "1.19.0",
        "content-disposition": "0.5.3",
        "content-type": "~1.0.4",
        "cookie": "0.4.0",
        "cookie-signature": "1.0.6",
        "debug": "2.6.9",
        "depd": "~1.1.2",
        "encodeurl": "~1.0.2",
        "escape-html": "~1.0.3",
        "etag": "~1.8.1",
        "finalhandler": "~1.1.2",
        "fresh": "0.5.2",
        "merge-descriptors": "1.0.1",
        "methods": "~1.1.2",
        "on-finished": "~2.3.0",
        "parseurl": "~1.3.3",
        "path-to-regexp": "0.1.7",
        "proxy-addr": "~2.0.5",
        "qs": "6.7.0",
        "range-parser": "~1.2.1",
        "safe-buffer": "5.1.2",
        "send": "0.17.1",
        "serve-static": "1.14.1",
        "setprototypeof": "1.1.1",
        "statuses": "~1.5.0",
        "type-is": "~1.6.18",
        "utils-merge": "1.0.1",
        "vary": "~1.1.2"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          }
        }
      }
    },
    "finalhandler": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
      "requires": {
        "debug": "2.6.9",
        "encodeurl": "~1.0.2",
        "escape-html": "~1.0.3",
        "on-finished": "~2.3.0",
        "parseurl": "~1.3.3",
        "statuses": "~1.5.0",
        "unpipe": "~1.0.0"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          }
        }
      }
    },
    "forwarded": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
    },
    "fresh": {
      "version": "0.5.2",
      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",


@@ 300,24 155,11 @@
        "toidentifier": "1.0.0"
      }
    },
    "iconv-lite": {
      "version": "0.4.24",
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
      "requires": {
        "safer-buffer": ">= 2.1.2 < 3"
      }
    },
    "inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
    },
    "ipaddr.js": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
    },
    "is-generator-function": {
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz",


@@ 416,21 258,6 @@
      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
    },
    "merge-descriptors": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
    },
    "methods": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
    },
    "mime": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
    },
    "mime-db": {
      "version": "1.42.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",


@@ 500,60 327,6 @@
      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
    },
    "path-to-regexp": {
      "version": "0.1.7",
      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
    },
    "proxy-addr": {
      "version": "2.0.6",
      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
      "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
      "requires": {
        "forwarded": "~0.1.2",
        "ipaddr.js": "1.9.1"
      }
    },
    "qs": {
      "version": "6.7.0",
      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
    },
    "range-parser": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
    },
    "raw-body": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
      "requires": {
        "bytes": "3.1.0",
        "http-errors": "1.7.2",
        "iconv-lite": "0.4.24",
        "unpipe": "1.0.0"
      },
      "dependencies": {
        "http-errors": {
          "version": "1.7.2",
          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
          "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
          "requires": {
            "depd": "~1.1.2",
            "inherits": "2.0.3",
            "setprototypeof": "1.1.1",
            "statuses": ">= 1.5.0 < 2",
            "toidentifier": "1.0.0"
          }
        },
        "inherits": {
          "version": "2.0.3",
          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
        }
      }
    },
    "resolve-path": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz",


@@ 591,64 364,6 @@
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
    },
    "safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
    },
    "send": {
      "version": "0.17.1",
      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
      "requires": {
        "debug": "2.6.9",
        "depd": "~1.1.2",
        "destroy": "~1.0.4",
        "encodeurl": "~1.0.2",
        "escape-html": "~1.0.3",
        "etag": "~1.8.1",
        "fresh": "0.5.2",
        "http-errors": "~1.7.2",
        "mime": "1.6.0",
        "ms": "2.1.1",
        "on-finished": "~2.3.0",
        "range-parser": "~1.2.1",
        "statuses": "~1.5.0"
      },
      "dependencies": {
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          },
          "dependencies": {
            "ms": {
              "version": "2.0.0",
              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
            }
          }
        },
        "ms": {
          "version": "2.1.1",
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
        }
      }
    },
    "serve-static": {
      "version": "1.14.1",
      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
      "requires": {
        "encodeurl": "~1.0.2",
        "escape-html": "~1.0.3",
        "parseurl": "~1.3.3",
        "send": "0.17.1"
      }
    },
    "setprototypeof": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",


@@ 704,21 419,11 @@
        "mime-types": "~2.1.24"
      }
    },
    "unpipe": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
    },
    "upath": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
      "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="
    },
    "utils-merge": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
    },
    "vary": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

M package.json => package.json +1 -2
@@ 1,6 1,6 @@
{
  "name": "bogbook",
  "version": "1.8.2",
  "version": "1.8.1",
  "description": "secure blockchain logging (blogging, without the l) -- bogging",
  "main": "server.js",
  "scripts": {


@@ 11,7 11,6 @@
  "license": "MIT",
  "dependencies": {
    "ed2curve": "^0.2.1",
    "express": "^4.17.1",
    "koa": "^2.11.0",
    "koa-static-server": "^1.4.0",
    "open": "^6.2.0",

M render.js => render.js +5 -7
@@ 25,8 25,8 @@ function getHeader (post, keys, mini) {
    ]),
    h('p', [
      h('a', {href: '#' + post.author}, [
        getImage(post.author, keys),
        getName(post.author, keys)
        getQuickImage(post.author, keys),
        getQuickName(post.author, keys)
      ]),
      mini 
    ])


@@ 38,7 38,7 @@ function render (msg, keys, preview) {
  var messageDiv = h('div', {id: msg.key})
  var message = h('div', {classList: 'message'})

  readBog().then(log => {
  bog().then(log => {
    if (log) {
      log.reverse().forEach(function (nextPost) {
        if (nextPost.edited == msg.key) {


@@ 171,11 171,9 @@ function render (msg, keys, preview) {
    message.appendChild(getHeader(msg, keys, mini))

    if (msg.reply) {
      get(msg.reply).then(msgg => {
        if (!msgg) {
      getTitle(msg.reply).then(title => {
        if (!title) {
          title = msg.reply.substring(0, 15) + '…'
        } else {
          title = msgg.text.substring(0, 15) + '…'
        }
        mini.appendChild(h('span', [
          '↳ ',

M server.js => server.js +218 -131
@@ 1,152 1,239 @@
var express = require('express')
var fs = require('fs')
var homedir = require('os').homedir()

var path = homedir + '/.bogbook/'
var bogdir = path + 'bogs/'
var confpath = path + 'config.json'

if (!fs.existsSync(homedir + '/.bogbook/')) {fs.mkdirSync(homedir + '/.bogbook/')}
if (!fs.existsSync(bogdir)){fs.mkdirSync(bogdir)}

if (fs.existsSync(confpath)) {
  console.log('loading config from ' + confpath)
  var config = require(confpath)
} else {
  var config = {
    port: '8089',
    wsport: '8080',
    url: 'localhost',
    author: '@Q++V5BbvWIg8B+TqtC9ZKFhetruuw+nOgxEqfjlOZI0='
  }
  config.fullurl = 'http://' + config.url + ':' + config.port + '/' 
  fs.writeFileSync(confpath, JSON.stringify(config), 'utf-8')
}

console.log(config)

if (process.argv[2] === 'verbose') {
  var VERBOSE = true
} else {
  var VERBOSE = false
}

console.log('Verbose output is ' + VERBOSE + ' run with `node server verbose` to see all output')

// log messages

function printAsk(req, unboxedreq) {
  if (VERBOSE) {
    console.log(req.requester + ' asked for feed ' + unboxedreq.author + ' after sequence ' + unboxedreq.seq)
  }
}

function printNewFeed (msg, req) {
  if (VERBOSE) 
    console.log('Saved full log of ' + msg.author + ' sent by ' + req.requester)
  else 
    console.log('NEW FEED from ' + msg.author)
}

function printUpdateFeed (msg, req) {
  if (VERBOSE)
    console.log('combined existing feed of ' + msg.author + ' sent from ' + req.requester + ' with diff and saved to server')
  else
    console.log('NEW UPDATE from ' + msg.author)
}

function printNoFeed (msg, req) {
  if (VERBOSE) { 
    console.log('We don\'t have the log on the server, requesting log from ' + req.requester )
  }
}

function printClientLonger (msg, req) {
  if (VERBOSE) {
    console.log(req.requester + '\'s feed of ' + msg.author  + ' is longer, requesting diff from ' + req.requester)
  }
}

function printClientShorter (msg, req, baserange, endrange) {
  if (VERBOSE) {
    console.log(req.requester + ' feed of ' + msg.author + ' is shorter, sending from ' + baserange + ' to ' + endrange + ' to ' + req.requester)
  }
}

function printFeedIdentical (msg, req) {
  if (VERBOSE) { 
    console.log(msg.author + '\'s feed sent from ' + req.requester + ' is identical')
  }
}

// static server (8089)

var serve = require('koa-static-server')
var koa = require('koa')
var open = require('open')
var bog = require('./bog')

var PORT = 8089
var app = new koa()

// namespace redirect -- add namespaces to ~/.bogbook/names.json
app.use(async function (ctx, next) {
  if (ctx.request.url[1] != '#') {
    var name = ctx.request.url.substring(1)

var app = express()
    if (!fs.existsSync(path + 'names.json')) {
      var obj = {
        ev: '@Q++V5BbvWIg8B+TqtC9ZKFhetruuw+nOgxEqfjlOZI0=',
        mil3s: '@531mT2x1FnQdpYJxVrG8YD9wiE767xO88kKRhi5A3Yg=',
        g: '@WVBPY53Bl4aUIngt2TXV8nW+IGKvCTqhv88EvktOX9s='
      }
      fs.writeFileSync(path + 'names.json', JSON.stringify(obj), 'UTF-8')
    }

var masterlog = []
    var obj = JSON.parse(fs.readFileSync(path + 'names.json', 'UTF-8'))

bog.readBog().then(log => {
  masterlog = log
    for (var property in obj) {
      if ((name === property) || (name === property + '/')) {
        ctx.redirect('/#' + obj[property])
      }
    }
  }
  return await next()
})

bog.keys().then(keys => {
  app.use(express.static('./'))
  app.use(function (req, res, next) {
    res.setHeader('Cache-Control', 'no-cache')
    res.setHeader('Content-Type', 'text/event-stream')
    res.setHeader('Connection', 'keep-alive')
app.use(serve({rootDir: '.', notFoundFile: 'index.html'}))

    if (req.url === '/pubkey') {
      res.write(Date.now() + '\ndata:' + keys.publicKey + '\n\n')
    }
app.listen(config.port)

    if (req.url[1] === '@') {
      var pubkey = req.url.substring(1, 46)
      var boxed = req.url.substring(46)
      bog.unbox(boxed, pubkey, keys).then(unboxed => {
        if (unboxed[0] === '@') {
          console.log('CLIENT:' + unboxed)
          var feed = unboxed.substring(0, 45)
          var clientseq = unboxed.substring(45)
          bog.readBog(feed).then(log => {
            if (!log[0]) {
              var seq = 0
              var memo = feed + seq
              console.log('SERVER:' + memo)
              if (clientseq > seq) {
                bog.box(memo, pubkey, keys).then(nextboxed => {
                  res.write(Date.now() + '\ndata:' + keys.publicKey + nextboxed + '\n\n')
                  next()
                })
              } 
              if (seq > clientseq) {
                // send more feed to the client
                console.log('SEND MORE FEED TO CLIENT')
                var endrange = log.length - clientseq - 25
                if (endrange < 0) {
                  endrange = log.length - clientseq - 1
                }
                var baserange = log.length - clientseq
                var diff = JSON.stringify(log.slice(endrange, baserange))
                
                bog.box(diff, pubkey, keys).then(finalboxed => {
                  res.write(Date.now() + '\ndata:' + keys.publicKey + finalboxed + '\n\n') 
                  next()
                })
                
              } else { next() } 
            } else {
              bog.open(log[0]).then(opened => {
                var seq = opened.seq
                var memo = feed + seq
                console.log('SERVER:' + memo)
                if (clientseq > seq) {
                  bog.box(memo, pubkey, keys).then(nextboxed => {
                    res.write(Date.now() + '\ndata:' + keys.publicKey + nextboxed + '\n\n')
                    next()
                  })
                }

                if (seq > clientseq) {
                  // send more feed to the client
                  console.log('SEND MORE FEED TO CLIENT')
                  var endrange = log.length - clientseq - 25
                  if (endrange < 0) {
                    endrange = log.length - clientseq - 1
                  }
                  var baserange = log.length - clientseq
                  var diff = JSON.stringify(log.slice(endrange, baserange))
                  bog.box(diff, pubkey, keys).then(finalboxed => {
                    res.write(Date.now() + '\ndata:' + keys.publicKey + finalboxed + '\n\n')
                    next()
                  })
                }
open(config.fullurl)

                else {
                  bog.box('Thank you.', pubkey, keys).then(finalboxed => {
                    res.write(Date.now() + '\ndata:' + keys.publicKey + finalboxed + '\n\n')
                    next()
                  })
                }
              })
            }
          })
        }

        if (unboxed[0] === '[') {
          console.log('RECEIVED MORE FEED FROM THE CLIENT')
          var newfeed = JSON.parse(unboxed)
          console.log(newfeed)

          bog.open(newfeed[0]).then(msg => {
            bog.readBog(msg.author).then(feed => {
              if (!feed[0]) {
                bog.writeBog(msg.author, newfeed)
                for (var i = newfeed.length -1; i >= 0; --i) {
                  bog.open(newfeed[i]).then(opened => {
                    console.log('new msg from ' + opened.author)
                    masterlog.unshift(opened)
                    if (opened.seq === newfeed.length) {
                      bog.writeBog('log', masterlog)
                      next()
                    }
                  })
                }
              }
console.log('Bogbook is running at: ' + config.fullurl)

              if (feed[0]) {
                bog.open(feed[0]).then(lastmsg => {
                  if (newfeed.length + lastmsg.seq === msg.seq) {
                    var newlog = newfeed.concat(feed)
                    bog.writeBog(msg.author, newlog)
                    for (var i = newfeed.length -1; i >= 0; --i) {
                      bog.open(newfeed[i]).then(opened => {
                        console.log('new msg from ' + opened.author)
                        masterlog.unshift(opened)
                        if (newfeed.length + lastmsg.seq === opened.seq) {
                          bog.writeBog('log', masterlog)
                          next()
var bog = require('./bog')
var WS = require('ws')
var nacl = require('tweetnacl')
    nacl.util = require('tweetnacl-util')

var wserve = new WS.Server({ port: config.wsport })

bog.keys().then(key => {
  wserve.on('connection', function (ws) {
    ws.on('message', function (message) {
      var req = JSON.parse(message)
      if (req.sendpub) {
        ws.send(key.publicKey)
        ws.close()
      } else { 
        bog.unbox(req.box, req.requester, key).then(unboxed => {
          var unboxedreq = JSON.parse(unboxed)
          //console.log(unboxedreq)
          if (unboxedreq.seq >= 0) {
            printAsk(req, unboxedreq)
            fs.readFile(bogdir + unboxedreq.author, 'UTF-8', function (err, data) {
              if (data) {
                var feed = JSON.parse(data)
                bog.open(feed[0]).then(msg => {
                  if (unboxedreq.seq === msg.seq) { 
                    printFeedIdentical(msg, req)
                    ws.close()
                  }  
                  if (unboxedreq.seq > msg.seq) {
                    printClientLonger(msg, req)
                    var reqdiff = JSON.stringify({author: unboxedreq.author, seq: msg.seq})
                    bog.box(reqdiff, req.requester, key).then(boxed => {
                      var obj = {
                        requester: key.publicKey,
                        box: boxed
                      }
                      ws.send(JSON.stringify(obj))
                    })
                  }
                  if (unboxedreq.seq < msg.seq) {
                    var endrange = feed.length - unboxedreq.seq - Math.floor(Math.random() * 50 + 1)
                    if (endrange < 0) {
                      endrange = 0
                    }
                    var baserange = feed.length - unboxedreq.seq
                    printClientShorter(msg, req, baserange, endrange)
                    if (baserange > 50) {
                      var latest = JSON.stringify({
                        latest: unboxedreq.author,
                        feed: feed.slice(0, 5)
                      })
                      bog.box(latest, req.requester, key).then(boxed => {
                        var obj = {
                          requester: key.publicKey,
                          box: boxed
                        }
                        //console.log('sending latest ' + unboxedreq.author)
                        ws.send(JSON.stringify(obj))
                      })
                    }
 
                    var diff = JSON.stringify(
                      feed.slice(
                        endrange, 
                        baserange
                      )
                    )
                    bog.box(diff, req.requester, key).then(boxed => {
                      var obj = {
                        requester: key.publicKey,
                        box: boxed
                      }
                      ws.send(JSON.stringify(obj))
                      ws.close()
                    })
                  }  
                }) 
              } else {
                printNoFeed(unboxedreq, req)
                var reqwhole = JSON.stringify({author: unboxedreq.author, seq: 0})

                bog.box(reqwhole, req.requester, key).then(boxed => {
                  var obj = {
                    requester: key.publicKey,
                    box: boxed  
                  }
                  ws.send(JSON.stringify(obj))
                })
              }
            })
          })
        }
      })
    }

    else {
      next()
    }

          } else if (Array.isArray(unboxedreq)) {
            bog.open(unboxedreq[0]).then(msg => {
              if (msg.seq === unboxedreq.length) {
                fs.writeFile(bogdir + msg.author, JSON.stringify(unboxedreq), 'UTF-8', function (err, success) {
                  printNewFeed(msg, req)
                })
              } if (msg.seq > unboxedreq.length) {
                fs.readFile(bogdir + msg.author, 'UTF-8', function (err, data) {
                  var feed = JSON.parse(data)
                  bog.open(feed[0]).then(lastmsg => {
                    if (unboxedreq.length + lastmsg.seq === msg.seq) {
                      var newlog = unboxedreq.concat(feed)
                      fs.writeFile(bogdir + msg.author, JSON.stringify(newlog), 'UTF-8', function (err, success) {
                        printUpdateFeed(msg, req)
                      })
                    }
                    ws.close()
                  })
                })
              } 
            })
          } 
        })
      }
    })
  })

  app.listen(PORT)
})


M settings.js => settings.js +15 -21
@@ 13,7 13,7 @@ function settingsPage (keys) {

  keyDiv.appendChild(h('button', {
    onclick: function () {
     removefeed('keypair').then(function () {
     localforage.removeItem('id', function () {
       location.hash = ''
       location.reload()
     })


@@ 25,7 25,7 @@ function settingsPage (keys) {
  keyDiv.appendChild(h('button', {
    onclick: function () {
      if (textarea.value) {
        pfs.writeFile('keypair', JSON.parse(textarea.value), 'utf8').then(function () { location.reload() })
        localforage.setItem('id', JSON.parse(textarea.value)).then(function () { location.reload() })
      }
    }
  }, ['Import Key']))


@@ 36,25 36,21 @@ function settingsPage (keys) {

  everything.appendChild(h('button', {
    onclick: function () {
      removeall().then(function () {
        location.hash = ''
        location.reload()
      })
      localforage.clear().then(function () {location.reload()})
    }
  }, ['Delete Everything']))

  /* we probably don't need this anymore
  var regenerate = h('div', {classList: 'message'})

  regenerate.appendChild(h('p', {innerHTML: marked('The regenerate button will create a new bogbook log in your browser from all of the feeds that you\'ve collected in your browser. While it is rare, you may use this button to troubleshoot if Bogbook is throwing strange database errors in your console.')}))

  regenerate.appendChild(h('button', {
    onclick: function () {
      regenerate().then(function () {
        location.hash = ''
	location.reload()
      })
      regenerate()
    }
  }, ['Regenerate']))
  }, ['Regenerate']))*/


  var pubs = h('div', {classList: 'message'})
 


@@ 62,14 58,14 @@ function settingsPage (keys) {

  var add = h('input', {placeholder: 'Add a pub'})

  readBog('isopubs').then(function (servers) {
  localforage.getItem('pubs').then(function (servers) {
    pubs.appendChild(h('div', [
      add,
      h('button', {
        onclick: function () {
          if (add.value) {
            servers.push({ws: add.value})
            writeBog('isopubs', servers).then(function () { location.hash = '' })
            servers.push(add.value)
            localforage.setItem('pubs', servers).then(function () { location.hash = '' })
          }
        }
      }, ['Add a pub'])


@@ 77,13 73,11 @@ function settingsPage (keys) {

    servers.forEach(function (pub) {
      pubs.appendChild(h('p', [
        pub.ws,
        pub,
        h('button', {
          onclick: function () {
            console.log(servers)
            var newServers = servers.filter(item => item.ws !== pub.ws)
            console.log(newServers)
            writeBog('isopubs', newServers).then(function () { location.hash = '' })
            var newServers = servers.filter(item => item !== pub)
            localforage.setItem('pubs', newServers).then(function () { location.hash = '' })
          }
        }, ['Remove'])
      ]))


@@ 92,7 86,7 @@ function settingsPage (keys) {

  pubs.appendChild(h('button', {
    onclick: function () {
      removefeed('isopubs').then(function () {
      localforage.removeItem('securepubs').then(function () {
        location.hash = ''
        location.reload()
      })


@@ 103,6 97,6 @@ function settingsPage (keys) {
  scroller.appendChild(pubs)
  scroller.appendChild(everything)
  scroller.appendChild(welcome)
  scroller.appendChild(regenerate)
  //scroller.appendChild(regenerate)
}


M views.js => views.js +18 -20
@@ 6,7 6,7 @@ function threadPage (src, keys) {

function getLoc (src) {
  var loc = h('span')
  readBog().then(log => {
  bog().then(log => {
    if (log) {
      for (var i = 0; i < log.length; i++) {
        if (((log[i].located === src) && (log[i].author === src)) || ((log[i].located === src.key) && (log[i].author === src.author))) {


@@ 36,7 36,7 @@ function profilePage (src, keys) {

  function getDesc (src) {
    var desc = h('span')
    readBog().then(log => {
    bog().then(log => {
      if (log) {
        for (var i = 0; i < log.length; i++) {
          if ((log[i].descripted === src) && (log[i].author === src)) {


@@ 50,7 50,7 @@ function profilePage (src, keys) {
  }

  function getBg (src, profile) {
    readBog().then(log => {
    bog().then(log => {
      if (log) {
        for (var i = 0; i < log.length; i++) {
          if ((log[i].backgrounded === src) && (log[i].author === src)) {


@@ 93,29 93,27 @@ function profilePage (src, keys) {

    profile.appendChild(h('button', {
      onclick: function () {
        removefeed(src).then(function () {
          regenerate().then(function () {
            location.hash = ''
            location.reload()
          })
        localforage.removeItem(src).then(function () {
          var home = true
          regenerate(home)
        })
      }
    }, ['Delete ' + name + '\'s feed']))
 
    if (src != keys.publicKey) {
      readBog('subscriptions').then(function (subs) {
      localforage.getItem('subscriptions').then(function (subs) {
        if (subs.includes(src)) {
          profile.appendChild(h('button', {
            onclick: function () {
              subs = subs.filter(a => a !== src)
              writeBog('subscriptions', subs).then(function () { location.hash = '' })
              localforage.setItem('subscriptions', subs).then(function () { location.hash = '' })
            }
          }, ['Unsubscribe from ' + name]))
        } else {
          profile.appendChild(h('button', {
            onclick: function () {
              subs.push(src)
              writeBog('subscriptions', subs).then(function () { location.hash = '' })
              localforage.setItem('subscriptions', subs).then(function () { location.hash = '' })
            }
          }, ['Subscribe to ' + name]))
        }


@@ 133,7 131,7 @@ function profilePage (src, keys) {
      }
    })
  }
  readBog().then(log => {
  bog().then(log => {
    var index = 0
    if (log) {
      var posts = log.slice(index, index + 33)


@@ 163,7 161,7 @@ function searchPage (src, keys) {
      }
    })
  }
  readBog().then(log => {
  bog().then(log => {
    if (log) {
      addPosts(log, keys)
    }


@@ 172,20 170,20 @@ function searchPage (src, keys) {

function publicPage (keys) {

  readBog('log').then(log => {
  localforage.getItem('log').then(log => {
    if (log) {
      log.sort((a, b) => a.timestamp - b.timestamp)
      var reversed = log.reverse()
      writeBog('log', reversed)
      localforage.setItem('log', reversed)
    }
  })

  readBog('subscriptions').then(subs => {
  localforage.getItem('subscriptions').then(subs => {
    if (subs) {
      if (!subs[1]) {
      if (subs.length === 1) {
        var subs = [keys.publicKey, '@Q++V5BbvWIg8B+TqtC9ZKFhetruuw+nOgxEqfjlOZI0=', '@WVBPY53Bl4aUIngt2TXV8nW+IGKvCTqhv88EvktOX9s=']
        console.log(subs)
        writeBog('subscriptions', subs)
        localforage.setItem('subscriptions', subs)
      } 
      subs.forEach(function (sub, index) {
        var timer = setInterval(function () {


@@ 197,7 195,7 @@ function publicPage (keys) {
    } else {
      var subs = [keys.publicKey, '@Q++V5BbvWIg8B+TqtC9ZKFhetruuw+nOgxEqfjlOZI0=', '@WVBPY53Bl4aUIngt2TXV8nW+IGKvCTqhv88EvktOX9s=']
      console.log(subs)
      writeBog('subscriptions', subs)
      localforage.setItem('subscriptions', subs)
    }
  })



@@ 209,7 207,7 @@ function publicPage (keys) {
    })
  }

  readBog().then(log => {
  bog().then(log => {
    var index = 0
    if (log) {
      var posts = log.slice(index, index + 25)