diff --git a/SPEC/NAME.md b/SPEC/NAME.md index 70902afb..7b459522 100644 --- a/SPEC/NAME.md +++ b/SPEC/NAME.md @@ -1,6 +1,9 @@ # Name API * [name.publish](#namepublish) +* [name.pubsub.cancel](#namepubsubcancel) +* [name.pubsub.state](#namepubsubstate) +* [name.pubsub.subs](#namepubsubsubs) * [name.resolve](#nameresolve) #### `name.publish` @@ -55,6 +58,89 @@ ipfs.name.publish(addr, function (err, res) { This way, you can republish a new version of your website under the same address. By default, `ipfs.name.publish` will use the Peer ID. If you want to have multiple websites (for example) under the same IPFS module, you can always check the [key API](./KEY.md). +#### `name.pubsub.cancel` + +> Cancel a name subscription. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.name.pubsub.cancel(arg, [callback]) + +`arg` is the name of the subscription to cancel. + +`callback` must follow `function (err, result) {}` signature, where `err` is an error if the operation was not successful. `result` is an object that contains the result of the operation, such as: + +```JavaScript +{ + canceled: true +} +``` + +If no `callback` is passed, a promise is returned. + +**Example:** + +```JavaScript +const name = 'QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm' + +ipfs.name.pubsub.cancel(name, function (err, result) { + console.log(result.canceled) + // true +}) +``` + +#### `name.pubsub.state` + +> Query the state of IPNS pubsub. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.name.pubsub.state([callback]) + +`callback` must follow `function (err, result) {}` signature, where `err` is an error if the operation was not successful. `result` is an object that contains the result of the operation, such as: + +```JavaScript +{ + enabled: true +} +``` + +If no `callback` is passed, a promise is returned. + +**Example:** + +```JavaScript +ipfs.name.pubsub.state(function (err, result) { + console.log(result.enabled) + // true +}) +``` + +#### `name.pubsub.subs` + +> Show current name subscriptions. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.name.pubsub.subs([callback]) + +`callback` must follow `function (err, result) {}` signature, where `err` is an error if the operation was not successful. `result` is an array of subscriptions, such as: + +```JavaScript +['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] +``` + +If no `callback` is passed, a promise is returned. + +**Example:** + +```JavaScript +ipfs.name.pubsub.subs(function (err, result) { + console.log(result) + // ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] +}) +``` + #### `name.resolve` > Resolve an IPNS name. diff --git a/js/src/index.js b/js/src/index.js index 81b9eb96..01905f17 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -11,6 +11,7 @@ exports.key = require('./key') exports.ls = require('./ls') exports.miscellaneous = require('./miscellaneous') exports.name = require('./name') +exports.namePubsub = require('./name-pubsub') exports.object = require('./object') exports.pin = require('./pin') exports.ping = require('./ping') diff --git a/js/src/name-pubsub/cancel.js b/js/src/name-pubsub/cancel.js new file mode 100644 index 00000000..4c08adec --- /dev/null +++ b/js/src/name-pubsub/cancel.js @@ -0,0 +1,87 @@ +/* eslint max-nested-callbacks: ["error", 5] */ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const loadFixture = require('aegir/fixtures') + +const { spawnNodeWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +const fixture = Object.freeze({ + data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') +}) + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.name.pubsub.cancel', function () { + let ipfs + let nodeId + let value + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodeWithId(factory, (err, node) => { + expect(err).to.not.exist() + + ipfs = node + nodeId = node.peerId.id + + ipfs.files.add(fixture.data, { pin: false }, (err, res) => { + expect(err).to.not.exist() + + value = res[0].path + done() + }) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should return false when the name that is intended to cancel is not subscribed', function (done) { + this.timeout(60 * 1000) + + ipfs.name.pubsub.cancel(nodeId, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.have.property('canceled') + expect(res.canceled).to.eql(false) + + done() + }) + }) + + it('should cancel a subscription correctly returning true', function (done) { + this.timeout(300 * 1000) + const ipnsPath = `/ipns/${nodeId}` + + series([ + (cb) => ipfs.name.pubsub.subs(cb), + (cb) => ipfs.name.publish(value, { resolve: false }, cb), + (cb) => ipfs.name.resolve(nodeId, cb), + (cb) => ipfs.name.pubsub.subs(cb), + (cb) => ipfs.name.pubsub.cancel(ipnsPath, cb), + (cb) => ipfs.name.pubsub.subs(cb) + ], (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res[0]).to.eql([]) // initally empty + expect(res[4]).to.have.property('canceled') + expect(res[4].canceled).to.eql(true) + expect(res[5]).to.be.an('array').that.does.not.include(ipnsPath) + + done() + }) + }) + }) +} diff --git a/js/src/name-pubsub/index.js b/js/src/name-pubsub/index.js new file mode 100644 index 00000000..1e6d4fc8 --- /dev/null +++ b/js/src/name-pubsub/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + cancel: require('./cancel'), + state: require('./state'), + subs: require('./subs') +} + +module.exports = createSuite(tests) diff --git a/js/src/name-pubsub/state.js b/js/src/name-pubsub/state.js new file mode 100644 index 00000000..c153c0d0 --- /dev/null +++ b/js/src/name-pubsub/state.js @@ -0,0 +1,47 @@ +/* eslint-env mocha */ +'use strict' + +const { spawnNodeWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.name.pubsub.state', function () { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodeWithId(factory, (err, node) => { + expect(err).to.not.exist() + + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get the current state of pubsub', function (done) { + this.timeout(50 * 1000) + + ipfs.name.pubsub.state((err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.have.property('enabled') + expect(res.enabled).to.be.eql(true) + + done() + }) + }) + }) +} diff --git a/js/src/name-pubsub/subs.js b/js/src/name-pubsub/subs.js new file mode 100644 index 00000000..958d23a0 --- /dev/null +++ b/js/src/name-pubsub/subs.js @@ -0,0 +1,81 @@ +/* eslint max-nested-callbacks: ["error", 5] */ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const loadFixture = require('aegir/fixtures') + +const { spawnNodeWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +const fixture = Object.freeze({ + data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') +}) + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.name.pubsub.subs', function () { + let ipfs + let nodeId + let value + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodeWithId(factory, (err, node) => { + expect(err).to.not.exist() + + ipfs = node + nodeId = node.peerId.id + + ipfs.files.add(fixture.data, { pin: false }, (err, res) => { + expect(err).to.not.exist() + + value = res[0].path + done() + }) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get an empty array as a result of subscriptions before any resolve', function (done) { + this.timeout(60 * 1000) + + ipfs.name.pubsub.subs((err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.eql([]) + + done() + }) + }) + + it('should get the list of subscriptions updated after a resolve', function (done) { + this.timeout(300 * 1000) + + series([ + (cb) => ipfs.name.pubsub.subs(cb), + (cb) => ipfs.name.publish(value, { resolve: false }, cb), + (cb) => ipfs.name.resolve(nodeId, cb), + (cb) => ipfs.name.pubsub.subs(cb) + ], (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res[0]).to.eql([]) // initally empty + expect(res[3]).to.be.an('array').that.does.include(`/ipns/${nodeId}`) + + done() + }) + }) + }) +}