From 5f276b2a60a7d60cc00a9144a3d535d9a1091d1b Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 31 Aug 2018 15:57:16 +0100 Subject: [PATCH 1/4] docs: ipns over pubsub --- SPEC/NAME.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/SPEC/NAME.md b/SPEC/NAME.md index 70902afb..5b692db4 100644 --- a/SPEC/NAME.md +++ b/SPEC/NAME.md @@ -2,6 +2,9 @@ * [name.publish](#namepublish) * [name.resolve](#nameresolve) +* [name.pubsub.cancel](#namepubsubcancel) +* [name.pubsub.state](#namepubsubstate) +* [name.pubsub.subs](#namepubsubsubs) #### `name.publish` @@ -76,6 +79,8 @@ This way, you can republish a new version of your website under the same address `callback` must follow `function (err, name) {}` signature, where `err` is an error if the operation was not successful. `name` is a string that contains the IPFS hash. +`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 resulting path, such as: + If no `callback` is passed, a promise is returned. **Example:** @@ -84,8 +89,93 @@ If no `callback` is passed, a promise is returned. // The IPNS address you want to resolve. const addr = '/ipns/ipfs.io' -ipfs.name.resolve(addr, function (err, name) { - console.log(name) +ipfs.name.resolve(addr, function (err, result) { + console.log(result.path) // /ipfs/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm }) ``` + +#### `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 object that contains the result of the operation, such as: + +```JavaScript +{ + strings: ['QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] +} +``` + +If no `callback` is passed, a promise is returned. + +**Example:** + +```JavaScript +ipfs.name.pubsub.subs(function (err, result) { + console.log(result.strings) + // ['QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] +}) +``` From 5b4645c97ea9cc8be4c4129523bb7fb461da9883 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 31 Aug 2018 16:55:48 +0100 Subject: [PATCH 2/4] feat: ipns over pubsub --- SPEC/NAME.md | 4 +- js/src/index.js | 1 + js/src/name-pubsub/cancel.js | 80 ++++++++++++++++++++++++++++++++++++ js/src/name-pubsub/index.js | 10 +++++ js/src/name-pubsub/state.js | 47 +++++++++++++++++++++ js/src/name-pubsub/subs.js | 80 ++++++++++++++++++++++++++++++++++++ 6 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 js/src/name-pubsub/cancel.js create mode 100644 js/src/name-pubsub/index.js create mode 100644 js/src/name-pubsub/state.js create mode 100644 js/src/name-pubsub/subs.js diff --git a/SPEC/NAME.md b/SPEC/NAME.md index 5b692db4..ed3d8faf 100644 --- a/SPEC/NAME.md +++ b/SPEC/NAME.md @@ -165,7 +165,7 @@ ipfs.name.pubsub.state(function (err, result) { ```JavaScript { - strings: ['QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] + strings: ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] } ``` @@ -176,6 +176,6 @@ If no `callback` is passed, a promise is returned. ```JavaScript ipfs.name.pubsub.subs(function (err, result) { console.log(result.strings) - // ['QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] + // ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] }) ``` 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..497e125e --- /dev/null +++ b/js/src/name-pubsub/cancel.js @@ -0,0 +1,80 @@ +/* eslint max-nested-callbacks: ["error", 5] */ +/* eslint-env mocha */ +'use strict' + +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'), + cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' +}) + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.name.pubsub.cancel', function () { + let ipfs + let nodeId + + 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 }, 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(140 * 1000) + const value = fixture.cid + + ipfs.name.publish(value, { resolve: false }, (err, res) => { + expect(err).to.not.exist() + + ipfs.name.resolve(nodeId, (err) => { + expect(err).to.not.exist() + + 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(true) + + 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..0634bc83 --- /dev/null +++ b/js/src/name-pubsub/subs.js @@ -0,0 +1,80 @@ +/* eslint max-nested-callbacks: ["error", 5] */ +/* eslint-env mocha */ +'use strict' + +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'), + cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' +}) + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.name.pubsub.subs', function () { + let ipfs + let nodeId + + 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 }, done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get a null 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.have.property('strings') + expect(res.strings).to.eql(null) + + done() + }) + }) + + it('should get the list of subscriptions updated after a resolve', function (done) { + this.timeout(140 * 1000) + const value = fixture.cid + + ipfs.name.publish(value, { resolve: false }, (err, res) => { + expect(err).to.not.exist() + + ipfs.name.resolve(nodeId, (err) => { + expect(err).to.not.exist() + + ipfs.name.pubsub.subs((err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.have.property('strings') + expect(res.strings).to.be.an('array').that.does.include(`/ipns/${nodeId}`) + + done() + }) + }) + }) + }) + }) +} From 3962673ecd5bade4123b3bb1362c913293374359 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Fri, 28 Sep 2018 14:33:50 +0100 Subject: [PATCH 3/4] fix: typo --- SPEC/NAME.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SPEC/NAME.md b/SPEC/NAME.md index ed3d8faf..b6a5f29d 100644 --- a/SPEC/NAME.md +++ b/SPEC/NAME.md @@ -77,9 +77,7 @@ This way, you can republish a new version of your website under the same address } ``` -`callback` must follow `function (err, name) {}` signature, where `err` is an error if the operation was not successful. `name` is a string that contains the IPFS hash. - -`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 resulting path, such as: +`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 resulting path. If no `callback` is passed, a promise is returned. From 08a5b903ba605057673326474581427674a2d4e9 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 17 Oct 2018 17:18:36 +0100 Subject: [PATCH 4/4] fix: code review --- SPEC/NAME.md | 80 ++++++++++++++++++------------------ js/src/name-pubsub/cancel.js | 45 +++++++++++--------- js/src/name-pubsub/subs.js | 45 ++++++++++---------- 3 files changed, 88 insertions(+), 82 deletions(-) diff --git a/SPEC/NAME.md b/SPEC/NAME.md index b6a5f29d..7b459522 100644 --- a/SPEC/NAME.md +++ b/SPEC/NAME.md @@ -1,10 +1,10 @@ # Name API * [name.publish](#namepublish) -* [name.resolve](#nameresolve) * [name.pubsub.cancel](#namepubsubcancel) * [name.pubsub.state](#namepubsubstate) * [name.pubsub.subs](#namepubsubsubs) +* [name.resolve](#nameresolve) #### `name.publish` @@ -58,41 +58,6 @@ 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.resolve` - -> Resolve an IPNS name. - -##### `Go` **WIP** - -##### `JavaScript` - ipfs.name.resolve(value, [options, callback]) - -`value` is a IPNS address, such as: `/ipns/ipfs.io`. - -`options` is an object that may contain: - -```JavaScript -{ - recursive: // bool - Resolve until the result is not an IPNS name. Default: false. - nocache: // bool - Do not use cached entries. Default: false. -} -``` - -`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 resulting path. - -If no `callback` is passed, a promise is returned. - -**Example:** - -```JavaScript -// The IPNS address you want to resolve. -const addr = '/ipns/ipfs.io' - -ipfs.name.resolve(addr, function (err, result) { - console.log(result.path) - // /ipfs/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm -}) -``` - #### `name.pubsub.cancel` > Cancel a name subscription. @@ -159,12 +124,10 @@ ipfs.name.pubsub.state(function (err, result) { ##### `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 object that contains the result of the operation, such as: +`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 -{ - strings: ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] -} +['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] ``` If no `callback` is passed, a promise is returned. @@ -173,7 +136,42 @@ If no `callback` is passed, a promise is returned. ```JavaScript ipfs.name.pubsub.subs(function (err, result) { - console.log(result.strings) + console.log(result) // ['/ipns/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm'] }) ``` + +#### `name.resolve` + +> Resolve an IPNS name. + +##### `Go` **WIP** + +##### `JavaScript` - ipfs.name.resolve(value, [options, callback]) + +`value` is a IPNS address, such as: `/ipns/ipfs.io`. + +`options` is an object that may contain: + +```JavaScript +{ + recursive: // bool - Resolve until the result is not an IPNS name. Default: false. + nocache: // bool - Do not use cached entries. Default: false. +} +``` + +`callback` must follow `function (err, name) {}` signature, where `err` is an error if the operation was not successful. `name` is a string that contains the IPFS hash. + +If no `callback` is passed, a promise is returned. + +**Example:** + +```JavaScript +// The IPNS address you want to resolve. +const addr = '/ipns/ipfs.io' + +ipfs.name.resolve(addr, function (err, name) { + console.log(name) + // /ipfs/QmQrX8hka2BtNHa8N8arAq16TCVx5qHcb46c5yPewRycLm +}) +``` diff --git a/js/src/name-pubsub/cancel.js b/js/src/name-pubsub/cancel.js index 497e125e..4c08adec 100644 --- a/js/src/name-pubsub/cancel.js +++ b/js/src/name-pubsub/cancel.js @@ -2,14 +2,14 @@ /* 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'), - cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' + data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') }) module.exports = (createCommon, options) => { @@ -20,6 +20,7 @@ module.exports = (createCommon, options) => { 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 @@ -35,7 +36,12 @@ module.exports = (createCommon, options) => { ipfs = node nodeId = node.peerId.id - ipfs.files.add(fixture.data, { pin: false }, done) + ipfs.files.add(fixture.data, { pin: false }, (err, res) => { + expect(err).to.not.exist() + + value = res[0].path + done() + }) }) }) }) @@ -56,24 +62,25 @@ module.exports = (createCommon, options) => { }) it('should cancel a subscription correctly returning true', function (done) { - this.timeout(140 * 1000) - const value = fixture.cid - - ipfs.name.publish(value, { resolve: false }, (err, res) => { + 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) - ipfs.name.resolve(nodeId, (err) => { - expect(err).to.not.exist() - - 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(true) - - done() - }) - }) + done() }) }) }) diff --git a/js/src/name-pubsub/subs.js b/js/src/name-pubsub/subs.js index 0634bc83..958d23a0 100644 --- a/js/src/name-pubsub/subs.js +++ b/js/src/name-pubsub/subs.js @@ -2,14 +2,14 @@ /* 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'), - cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' + data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') }) module.exports = (createCommon, options) => { @@ -20,6 +20,7 @@ module.exports = (createCommon, options) => { 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 @@ -35,45 +36,45 @@ module.exports = (createCommon, options) => { ipfs = node nodeId = node.peerId.id - ipfs.files.add(fixture.data, { pin: false }, done) + 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 a null result of subscriptions before any resolve', function (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.have.property('strings') - expect(res.strings).to.eql(null) + expect(res).to.eql([]) done() }) }) it('should get the list of subscriptions updated after a resolve', function (done) { - this.timeout(140 * 1000) - const value = fixture.cid - - ipfs.name.publish(value, { resolve: false }, (err, res) => { + 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}`) - ipfs.name.resolve(nodeId, (err) => { - expect(err).to.not.exist() - - ipfs.name.pubsub.subs((err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.have.property('strings') - expect(res.strings).to.be.an('array').that.does.include(`/ipns/${nodeId}`) - - done() - }) - }) + done() }) }) })