hyperns-service
Version:
HyperNS service, based on autobase
162 lines (130 loc) • 4.77 kB
JavaScript
const b4a = require('b4a')
const ReadyResource = require('ready-resource')
const Autobase = require('autobase')
const HyperNS = require('hyperns')
const IdEnc = require('hypercore-id-encoding')
const BlindPeerClient = require('blind-peer/client')
const { resolveStruct } = require('./spec/hyperschema')
const opEncoding = resolveStruct('@hyperns/op')
const ops = {
ADD_WRITER: 0,
REMOVE_WRITER: 1,
ADD_BLIND_PEER: 2,
REMOVE_BLIND_PEER: 3,
ADD_RECORD: 4
}
class HyperNsService extends ReadyResource {
static OPS = ops
static VALUE_ENCODING = opEncoding
constructor (store, swarm, { bootstrap = null, ackInterval, encryptionKey, blindPeers } = {}) {
super()
// The name service is public, so requires no encryption
// But blind peers assume encrypted cores, so we have to set one
// No security is implied by the default obviously (nor is security needed)
encryptionKey = encryptionKey || b4a.alloc(30).fill('dummy-secret')
this.blindPeers = blindPeers
? blindPeers.map(p => IdEnc.decode(p))
: []
this.base = new Autobase(store, bootstrap, {
encryptionKey,
valueEncoding: opEncoding,
open (store) {
return new HyperNS(store.get('view'), { extension: false })
},
apply: this._apply.bind(this),
ackInterval
})
this.swarm = swarm
// We only handle blind-peer connections in this handler
// Another handler (not defined in this class) is responsible
// for setting up corestore replication etc
this.swarm.on('connection', async (conn, peer) => {
if (!this._isBlindPeer(conn.remotePublicKey)) return
try {
await this._setupBlindPeerConnection(conn, peer)
} catch (e) {
console.error(e) // TODO: emit an event instead
}
})
}
get view () {
return this.base.view
}
get dbKey () {
return this.base.view.db.engine.core.key
}
get dbDiscoveryKey () {
return this.base.view.db.engine.core.discoveryKey
}
get viewBlockEncryptionKey () {
return this.view.db.core.getBackingCore().session.encryption.blockKey
}
async _open () {
await this.base.ready()
for (const p of this.blindPeers) this.swarm.joinPeer(p)
}
async _close () {
for (const p of this.blindPeers) this.swarm.leavePeer(p)
await this.base.close()
}
// Must not be called directly, only from the autobase apply
async _apply (nodes, view, base) {
for (const node of nodes) {
if (node.value.op === ops.ADD_RECORD) {
const record = node.value.record
const { name, publicKey, blindPeers } = record
if (await view.get(name)) continue
await view.insert(name, publicKey, blindPeers)
this.emit('new-record', record) // TODO: tail the view and emit only after indexed
} else if (node.value.op === ops.ADD_BLIND_PEER) {
// TODO: rules for when someone can be added.
// This probably requires passing in both the blind-peer's
// swarm public key, and the key of their autobase (TBC)
await base.addWriter(node.value.blindPeerKey, { isIndexer: false })
} else if (node.value.op === ops.ADD_WRITER) {
// TODO: rules for when someone can be added (passed-in list?)
await base.addWriter(node.value.writerKey, { isIndexer: true })
}
}
}
async _setupBlindPeerConnection (conn, peerInfo) {
// TODO: lifecycle (early-return/cancel when closing)
this.emit('blind-peer-connection', peerInfo)
const peer = new BlindPeerClient(conn)
const info = await peer.addMailbox({ autobase: this.base.key })
if (info.open === false) {
await this.base.append({ op: HyperNsService.OPS.ADD_BLIND_PEER, blindPeerKey: info.writer })
await this.base.update()
const core = this.base.store.get({ key: info.writer, active: false })
await core.setEncryptionKey(this.base.encryptionKey)
const req = { autobase: this.base.key, blockEncryptionKey: core.encryption.blockKey }
await core.close()
await peer.addMailbox(req)
this.emit('add-mailbox', req)
}
}
_isBlindPeer (remotePublicKey) {
for (const key of this.blindPeers) {
if (b4a.equals(remotePublicKey, key)) return true
}
return false
}
async addRecord (name, publicKey, blindPeers) {
const record = { name, publicKey, blindPeers }
await this.base.append({ op: ops.ADD_RECORD, record })
}
async addBlindPeer (blindPeerKey) {
await this.base.append(
{ op: ops.ADD_BLIND_PEER, blindPeerKey }
)
}
async addWriter (writerKey) {
await this.base.append(
{ op: ops.ADD_WRITER, writerKey }
)
}
async getRecord (name) {
return await this.view.get(name)
}
}
module.exports = HyperNsService