UNPKG

hyperns-service

Version:
162 lines (130 loc) 4.77 kB
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