ipfs
Version:
JavaScript implementation of the IPFS specification
147 lines (117 loc) • 4.32 kB
JavaScript
const ipns = require('ipns')
const { fromB58String, toB58String } = require('multihashes')
const PubsubDatastore = require('datastore-pubsub')
const withIs = require('class-is')
const errcode = require('err-code')
const debug = require('debug')
const log = debug('ipfs:ipns:pubsub')
log.error = debug('ipfs:ipns:pubsub:error')
// Pubsub datastore aims to manage the pubsub subscriptions for IPNS
class IpnsPubsubDatastore {
constructor (pubsub, localDatastore, peerId) {
this._pubsub = pubsub
this._subscriptions = {}
// Bind _handleSubscriptionKey function, which is called by PubsubDatastore.
this._handleSubscriptionKey = this._handleSubscriptionKey.bind(this)
this._pubsubDs = new PubsubDatastore(pubsub, localDatastore, peerId, ipns.validator, this._handleSubscriptionKey)
}
/**
* Put a value to the pubsub datastore indexed by the received key properly encoded.
* @param {Buffer} key identifier of the value.
* @param {Buffer} value value to be stored.
* @param {function(Error)} callback
* @returns {void}
*/
put (key, value, callback) {
this._pubsubDs.put(key, value, callback)
}
/**
* Get a value from the pubsub datastore indexed by the received key properly encoded.
* Moreover, the identifier topic is subscribed and the pubsub datastore records will be
* updated once new publishes occur.
* @param {Buffer} key identifier of the value to be obtained.
* @param {function(Error, Buffer)} callback
* @returns {void}
*/
get (key, callback) {
this._pubsubDs.get(key, (err, res) => {
// Add topic subscribed
const ns = key.slice(0, ipns.namespaceLength)
if (ns.toString() === ipns.namespace) {
const stringifiedTopic = key.toString()
const id = toB58String(key.slice(ipns.namespaceLength))
this._subscriptions[stringifiedTopic] = id
log(`subscribed pubsub ${stringifiedTopic}: ${id}`)
}
// If no data was obtained, after storing the subscription, return the error.
if (err) {
return callback(err)
}
callback(null, res)
})
}
// Modify subscription key to have a proper encoding
_handleSubscriptionKey (key, callback) {
const subscriber = this._subscriptions[key]
if (!subscriber) {
const errMsg = `key ${key} does not correspond to a subscription`
log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_INVALID_KEY'))
}
let keys
try {
keys = ipns.getIdKeys(fromB58String(subscriber))
} catch (err) {
log.error(err)
return callback(err)
}
callback(null, keys.routingKey.toBuffer())
}
/**
* Get pubsub subscriptions related to ipns.
* @param {function(Error, Object)} callback
* @returns {void}
*/
getSubscriptions (callback) {
const subscriptions = Object.values(this._subscriptions).filter(Boolean)
return callback(null, subscriptions.map((sub) => `${ipns.namespace}${sub}`))
}
/**
* Cancel pubsub subscriptions related to ipns.
* @param {String} name ipns path to cancel the pubsub subscription.
* @param {function(Error, Object)} callback
* @returns {void}
*/
cancel (name, callback) {
if (typeof name !== 'string') {
const errMsg = `received subscription name is not valid`
log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_INVALID_SUBSCRIPTION_NAME'))
}
// Trim /ipns/ prefix from the name
if (name.startsWith(ipns.namespace)) {
name = name.substring(ipns.namespaceLength)
}
const stringifiedTopic = Object.keys(this._subscriptions).find((key) => this._subscriptions[key] === name)
// Not found topic
if (!stringifiedTopic) {
return callback(null, {
canceled: false
})
}
// Unsubscribe topic
try {
const bufTopic = Buffer.from(stringifiedTopic)
this._pubsubDs.unsubscribe(bufTopic)
} catch (err) {
return callback(err)
}
this._subscriptions[stringifiedTopic] = undefined
log(`unsubscribed pubsub ${stringifiedTopic}: ${name}`)
callback(null, {
canceled: true
})
}
}
exports = module.exports = withIs(IpnsPubsubDatastore, { className: 'IpnsPubsubDatastore', symbolName: '@js-ipfs/ipns/IpnsPubsubDatastore' })