libvms
Version:
API for running cryptographically auditable VMs.
165 lines (148 loc) • 3.9 kB
JavaScript
const hypercore = require('hypercore')
const concat = require('concat-stream')
const raf = require('random-access-file')
const ram = require('random-access-memory')
const swarmDefaults = require('dat-swarm-defaults')
const disc = require('discovery-swarm')
const debug = require('debug')('vms')
const DEFAULT_PORT = 3282
class CallLog {
constructor (_hc) {
this.hc = _hc
this.url = 'dat://' + _hc.key.toString('hex')
}
async close () {
await new Promise((resolve, reject) => {
this.hc.close(err => {
if (err) reject(err)
else resolve()
})
})
}
get length () {
return this.hc.length
}
async append (obj) {
debug('call log appending', obj)
await new Promise((resolve, reject) => {
this.hc.append(obj, err => {
if (err) reject(err)
else resolve()
})
})
}
appendInit ({code, filesArchiveUrl}) {
return this.append({
type: 'init',
filesArchiveUrl,
code
})
}
appendCall ({userId, methodName, args, res, err, filesVersion}) {
return this.append({
type: 'call',
call: {
userId,
methodName,
args
},
result: {
res,
err: err ? err.name || err.message : undefined,
filesVersion
}
})
}
get (seq, opts) {
return new Promise((resolve, reject) => {
this.hc.get(seq, opts, (err, res) => {
if (err) reject(err)
else resolve(res)
})
})
}
list ({start, end} = {}) {
return new Promise((resolve, reject) => {
const rs = this.hc.createReadStream({start, end})
rs.on('error', reject)
rs.pipe(concat({encoding: 'object'}, resolve))
})
}
}
exports.create = async function (dir, code, filesArchiveUrl) {
debug('creating new call log at', dir)
var hc = hypercore(storage(dir), {valueEncoding: 'json'})
await new Promise((resolve, reject) => {
hc.on('ready', resolve)
hc.on('error', reject)
})
joinSwarm(hc)
var log = new CallLog(hc)
await log.appendInit({code, filesArchiveUrl})
return log
}
exports.open = async function (dir) {
debug('opening existing call log at', dir)
var hc = hypercore(storage(dir), {valueEncoding: 'json'})
await new Promise((resolve, reject) => {
hc.on('ready', resolve)
hc.on('error', reject)
})
joinSwarm(hc)
return new CallLog(hc)
}
exports.fetch = async function (callLogUrl, dir) {
var key = datUrlToKey(callLogUrl)
debug('fetching existing call log, storing to', dir || 'memory')
debug('key is', key)
var hc = hypercore(dir ? storage(dir) : memory, key, {valueEncoding: 'json'})
await new Promise((resolve, reject) => {
hc.on('ready', resolve)
hc.on('error', reject)
})
joinSwarm(hc)
await new Promise((resolve, reject) => {
hc.on('sync', resolve)
hc.on('error', reject)
})
return new CallLog(hc)
}
function joinSwarm (hc) {
var swarm = disc(swarmDefaults({
hash: false,
stream: peer => {
var stream = hc.replicate({
upload: true,
download: true,
live: true
})
// stream.on('close', function () {
// debug('replication stream closed')
// })
// stream.on('error', function (err) {
// debug('replication error:', err.message)
// })
// stream.on('end', function () {
// debug('replication stream ended')
// })
return stream
}
}))
swarm.once('error', function () {
swarm.listen(0)
})
swarm.listen(DEFAULT_PORT) // this is probably colliding with the files archive
swarm.join(hc.discoveryKey, { announce: true })
}
function storage (directory) {
return filename => {
return raf('call-log.' + filename, {directory})
}
}
function memory (filename) {
return ram()
}
function datUrlToKey (url) {
var match = /[0-9a-f]{64}/i.exec(url)
return match ? match[0] : null
}