Skip to content

Commit

Permalink
Switch to Hyperdb (#2)
Browse files Browse the repository at this point in the history
* Switch to Hyperdb

* Update schemas

* standard

* Use correct naming in Schema

* Change ops naming
  • Loading branch information
supersuryaansh authored Feb 10, 2025
1 parent 94310ab commit 3bcc74b
Show file tree
Hide file tree
Showing 11 changed files with 1,298 additions and 85 deletions.
159 changes: 75 additions & 84 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

const Autobase = require('autobase')
const BlindPairing = require('blind-pairing')
const Hyperbee = require('hyperbee')
const HyperDB = require('hyperdb')
const Hyperswarm = require('hyperswarm')
const ReadyResource = require('ready-resource')
const z32 = require('z32')
const b4a = require('b4a')
const { Router, dispatch } = require('./spec/hyperdispatch')
const db = require('./spec/db/index.js')

class AutopassPairer extends ReadyResource {
constructor (store, invite, opts = {}) {
super()

this.store = store
this.invite = invite
this.swarm = null
Expand All @@ -27,24 +28,21 @@ class AutopassPairer extends ReadyResource {

async _open () {
await this.store.ready()

this.swarm = new Hyperswarm({
keyPair: await this.store.createKeyPair('hyperswarm'),
bootstrap: this.bootstrap
})

const store = this.store // we null this when passing it on, so avoid a nullptr
const store = this.store
this.swarm.on('connection', (connection, peerInfo) => {
store.replicate(connection)
})

this.pairing = new BlindPairing(this.swarm)

const core = Autobase.getLocalCore(this.store)
await core.ready()
const key = core.key
await core.close()

this.candidate = this.pairing.addCandidate({
invite: z32.decode(this.invite),
userData: key,
Expand All @@ -59,12 +57,23 @@ class AutopassPairer extends ReadyResource {
}
this.swarm = null
this.store = null
if (this.onresolve) this.onresolve(this.pass)
if (this.onresolve) this._whenWritable()
this.candidate.close().catch(noop)
}
})
}

_whenWritable () {
if (this.pass.base.writable) return
const check = () => {
if (this.pass.base.writable) {
this.pass.base.off('update', check)
this.onresolve(this.pass)
}
}
this.pass.base.on('update', check)
}

async _close () {
if (this.candidate !== null) {
await this.candidate.close()
Expand Down Expand Up @@ -96,7 +105,7 @@ class AutopassPairer extends ReadyResource {
class Autopass extends ReadyResource {
constructor (corestore, opts = {}) {
super()

this.router = new Router()
this.store = corestore
this.swarm = opts.swarm || null
this.base = null
Expand All @@ -105,6 +114,26 @@ class Autopass extends ReadyResource {
this.pairing = null
this.replicate = opts.replicate !== false
this.debug = !!opts.key
// Register handlers for commands
this.router.add('@autopass/remove-writer', async (data, context) => {
await context.base.removeWriter(data.key)
})

this.router.add('@autopass/add-writer', async (data, context) => {
await context.base.addWriter(data.key)
})

this.router.add('@autopass/put', async (data, context) => {
await context.view.insert('@autopass/records', data)
})

this.router.add('@autopass/del', async (data, context) => {
await context.view.delete('@autopass/records', { key: data.key })
})

this.router.add('@autopass/add-invite', async (data, context) => {
await context.view.insert('@autopass/invite', data)
})

this._boot(opts)
this.ready().catch(noop)
Expand All @@ -117,44 +146,33 @@ class Autopass extends ReadyResource {
this.base = new Autobase(this.store, key, {
encrypt: true,
encryptionKey,
valueEncoding: 'json',
open (store) {
return new Hyperbee(store.get('view'), {
return HyperDB.bee(store.get('view'), db, {
extension: false,
keyEncoding: 'utf-8',
valueEncoding: 'json'
autoUpdate: true
})
},
// New data blocks will be added using the apply function
async apply (nodes, view, base) {
for (const node of nodes) {
const op = node.value

// Add support for adding other peers as a writer to the base
if (op.type === 'addWriter') {
await base.addWriter(z32.decode(op.key))
} else if (op.type === 'removeWriter') {
await base.removeWriter(z32.decode(op.key))
} else if (op.type === 'addRecord') {
// This adds a new record
await view.put(op.key, op.value)
} else if (op.type === 'removeRecord') {
// Remove an existing record
await view.del(op.key)
}
}
}
apply: this._apply.bind(this)
})

this.base.on('update', () => this.emit('update'))
this.base.on('update', () => {
this.emit('update')
})
}

async _apply (nodes, view, base) {
for (const node of nodes) {
await this.router.dispatch(node.value, { view, base })
}
await view.flush()
}

async _open () {
await this.base.ready()
if (this.replicate) await this._replicate()
}

// Close the base
async _close () {
if (this.swarm) {
await this.member.close()
Expand All @@ -164,23 +182,18 @@ class Autopass extends ReadyResource {
await this.base.close()
}

// Need this key to become a writer
get writerKey () {
return this.base.local.key
}

// Return bootstrap key of the base
// This is what other peers should use to bootstrap the base from
get key () {
return this.base.key
}

// Find peers in Hyperswarm using this
get discoveryKey () {
return this.base.discoveryKey
}

// Encryption key for the base
get encryptionKey () {
return this.base.encryptionKey
}
Expand All @@ -191,101 +204,79 @@ class Autopass extends ReadyResource {

async createInvite (opts) {
if (this.opened === false) await this.ready()
const existing = await this.get('autopass/invite')
if (existing) return existing.invite
const existing = await this.base.view.findOne('@autopass/invite', {})
if (existing) {
return z32.encode(existing.invite)
}
const { id, invite, publicKey, expires } = BlindPairing.createInvite(this.base.key)
const record = { id: z32.encode(id), invite: z32.encode(invite), publicKey: z32.encode(publicKey), expires }
await this.add('autopass/invite', record)
return record.invite

const record = { id, invite, publicKey, expires }
await this.base.append(dispatch('@autopass/add-invite', record))
return z32.encode(record.invite)
}

// Get data of all indexes in the base
list (opts) {
return this.base.view.createReadStream(opts)
return this.base.view.find('@autopass/records', {})
}

// Get data stored in a specific key
async get (key) {
const node = await this.base.view.get(key)
if (node === null) return null
return node.value
const data = await this.base.view.get('@autopass/records', { key })
if (data === null) {
return null
}
return data.value
}

// Add a peer as a writer
async addWriter (key) {
await this.base.append({
type: 'addWriter',
key: b4a.isBuffer(key) ? z32.encode(key) : key
})

await this.base.append(dispatch('@autopass/add-writer', { key: b4a.isBuffer(key) ? key : b4a.from(key) }))
return true
}

// To later add removeWriter
async removeWriter (key) {
await this.base.append({
type: 'removeWriter',
key: b4a.isBuffer(key) ? z32.encode(key) : key
})
await this.base.append(dispatch('@autopass/remove-writer', { key: b4a.isBuffer(key) ? key : b4a.from(key) }))
}

// Check if the base is writable
get writable () {
return this.base.writable
}

// Start Replicating the base across peers
async _replicate () {
await this.base.ready()
if (this.swarm === null) {
this.swarm = new Hyperswarm({
keyPair: await this.store.createKeyPair('hyperswarm'),
bootstrap: this.bootstrap
})

this.swarm.on('connection', (connection, peerInfo) => {
this.store.replicate(connection)
})
}
this.pairing = new BlindPairing(this.swarm)

this.member = this.pairing.addMember({
discoveryKey: this.base.discoveryKey,
onadd: async (candidate) => {
const id = z32.encode(candidate.inviteId)
const inv = await this.get('autopass/invite')

if (inv.id !== id) return

candidate.open(z32.decode(inv.publicKey))

const id = candidate.inviteId
const inv = await this.base.view.findOne('@autopass/invite', {})
if (!b4a.equals(inv.id, id)) {
return
}
candidate.open(inv.publicKey)
await this.addWriter(candidate.userData)

candidate.confirm({
key: this.base.key,
encryptionKey: this.base.encryptionKey
})
}
})

this.swarm.join(this.base.discoveryKey)
}

// Append a key/value to the base
async add (key, value) {
await this.base.append({
type: 'addRecord',
key,
value
})
await this.base.append(dispatch('@autopass/put', { key, value }))
}

// Remove a key pair
async remove (key) {
await this.base.append({
type: 'removeRecord',
key,
value: null
})
await this.base.append(dispatch('@autopass/del', { key }))
}
} // end class

Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
"autobase": "^6.5.12",
"b4a": "^1.6.7",
"blind-pairing": "^2.3.1",
"hyperbee": "^2.20.7",
"compact-encoding": "^2.16.0",
"hyperbee": "^2.21.2",
"hypercore": "^10.38.2",
"hyperdb": "^4.8.0",
"hyperdispatch": "^1.0.2",
"hyperschema": "^1.7.2",
"hyperswarm": "^4.8.4",
"ready-resource": "^1.1.1"
}
Expand Down
Loading

0 comments on commit 3bcc74b

Please sign in to comment.