From c572348c7f969c97a63370b6f844274f659a1509 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 5 Feb 2016 13:38:30 +0100 Subject: [PATCH] feat: Add promise based api All methods return a promise when not passed a callback Closes #80 --- README.md | 14 ++++++ src/api/dht.js | 29 ++++++++++--- src/api/log.js | 5 +++ src/api/ping.js | 5 +++ src/request-api.js | 23 +++++++--- test/api/add.spec.js | 11 +++++ test/api/block.spec.js | 30 +++++++++++++ test/api/cat.spec.js | 16 +++++++ test/api/commands.spec.js | 9 ++++ test/api/config.spec.js | 33 ++++++++++++++ test/api/dht.spec.js | 23 ++++++++++ test/api/diag.spec.js | 18 ++++++++ test/api/id.spec.js | 10 +++++ test/api/log.spec.js | 12 ++++++ test/api/ls.spec.js | 29 +++++++++++++ test/api/name.spec.js | 20 +++++++++ test/api/object.spec.js | 91 +++++++++++++++++++++++++++++++++++++++ test/api/pin.spec.js | 30 +++++++++++++ test/api/ping.spec.js | 12 ++++++ test/api/refs.spec.js | 55 +++++++++++++---------- test/api/swarm.spec.js | 10 +++++ test/api/version.spec.js | 20 +++++++++ 22 files changed, 470 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 49b92f74e..de1c008b3 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,20 @@ If you omit the host and port, the api will parse `window.host`, and use this in var ipfs = window.ipfsAPI() ``` +### Using Promises + +If you do not pass in a callback all api functions will return a `Promise`, for example + +```js +ipfs.id() + .then(function (id) { + console.log('my id is: ', id) + }) +``` + +This relies on a global `Promise` object. If you are in an environemnt where that is not +yet available you need to bring your own polyfill. + #### Gotchas When using the api from script tag for things that require buffers (`ipfs.add`, for example), you will have to use either the exposed `ipfs.Buffer`, that works just like a node buffer, or use this [browser buffer](https://github.com/feross/buffer). diff --git a/src/api/dht.js b/src/api/dht.js index 129873763..5c70d0897 100644 --- a/src/api/dht.js +++ b/src/api/dht.js @@ -11,10 +11,10 @@ module.exports = send => { opts = null } - return send('dht/get', key, opts, null, (err, res) => { - if (err) return cb(err) - if (!res) return cb(new Error('empty response')) - if (res.length === 0) return cb(new Error('no value returned for key')) + const handleResult = (done, err, res) => { + if (err) return done(err) + if (!res) return done(new Error('empty response')) + if (res.length === 0) return done(new Error('no value returned for key')) // Inconsistent return values in the browser vs node if (Array.isArray(res)) { @@ -22,12 +22,27 @@ module.exports = send => { } if (res.Type === 5) { - cb(null, res.Extra) + done(null, res.Extra) } else { let error = new Error('key was not found (type 6)') - cb(error) + done(error) } - }) + } + + if (typeof cb !== 'function' && typeof Promise !== 'undefined') { + const done = (err, res) => { + if (err) throw err + return res + } + + return send('dht/get', key, opts) + .then( + res => handleResult(done, null, res), + err => handleResult(done, err) + ) + } + + return send('dht/get', key, opts, null, handleResult.bind(null, cb)) }, put (key, value, opts, cb) { if (typeof (opts) === 'function' && !cb) { diff --git a/src/api/log.js b/src/api/log.js index 3e3c515be..b246a8a09 100644 --- a/src/api/log.js +++ b/src/api/log.js @@ -5,6 +5,11 @@ const ndjson = require('ndjson') module.exports = send => { return { tail (cb) { + if (typeof cb !== 'function' && typeof Promise !== 'undefined') { + return send('log/tail', null, {}, null, false) + .then(res => res.pipe(ndjson.parse())) + } + return send('log/tail', null, {}, null, false, (err, res) => { if (err) return cb(err) cb(null, res.pipe(ndjson.parse())) diff --git a/src/api/ping.js b/src/api/ping.js index 43a519009..f4261e01e 100644 --- a/src/api/ping.js +++ b/src/api/ping.js @@ -2,6 +2,11 @@ module.exports = send => { return function ping (id, cb) { + if (typeof cb !== 'function' && typeof Promise !== 'undefined') { + return send('ping', id, {n: 1}, null) + .then(res => res[1]) + } + return send('ping', id, { n: 1 }, null, function (err, res) { if (err) return cb(err, null) cb(null, res[1]) diff --git a/src/request-api.js b/src/request-api.js index 23d39408e..2f6ef176f 100644 --- a/src/request-api.js +++ b/src/request-api.js @@ -63,11 +63,6 @@ function requestAPI (config, path, args, qs, files, buffer, cb) { if (args) qs.arg = args if (files && !Array.isArray(files)) files = [files] - if (typeof buffer === 'function') { - cb = buffer - buffer = false - } - if (qs.r) { qs.recursive = qs.r delete qs.r // From IPFS 0.4.0, it throw an error when both r and recursive are passed @@ -116,5 +111,21 @@ function requestAPI (config, path, args, qs, files, buffer, cb) { // -- Interface exports = module.exports = function getRequestAPI (config) { - return requestAPI.bind(null, config) + return function (path, args, qs, files, buffer, cb) { + if (typeof buffer === 'function') { + cb = buffer + buffer = false + } + + if (typeof cb !== 'function' && typeof Promise !== 'undefined') { + return new Promise(function (resolve, reject) { + requestAPI(config, path, args, qs, files, buffer, function (err, res) { + if (err) return reject(err) + resolve(res) + }) + }) + } + + return requestAPI(config, path, args, qs, files, buffer, cb) + } } diff --git a/test/api/add.spec.js b/test/api/add.spec.js index 6b49bb488..1432e299e 100644 --- a/test/api/add.spec.js +++ b/test/api/add.spec.js @@ -118,4 +118,15 @@ describe('.add', () => { done() }) }) + + describe('promise', () => { + it('add buffer', () => { + let buf = new Buffer(testfile) + return apiClients['a'].add(buf) + .then(res => { + expect(res).to.have.length(1) + expect(res[0]).to.have.property('Hash', 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') + }) + }) + }) }) diff --git a/test/api/block.spec.js b/test/api/block.spec.js index 3ff6ea83b..cedc9bd9f 100644 --- a/test/api/block.spec.js +++ b/test/api/block.spec.js @@ -34,4 +34,34 @@ describe('.block', () => { done() }) }) + + describe('promise', () => { + it('block.put', () => { + return apiClients['a'].block.put(blorb) + .then(res => { + expect(res).to.have.a.property('Key', 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + }) + + it('block.get', done => { + return apiClients['a'].block.get(blorbKey) + .then(res => { + let buf = '' + res + .on('data', function (data) { buf += data }) + .on('end', function () { + expect(buf).to.be.equal('blorb') + done() + }) + }) + }) + + it('block.stat', () => { + return apiClients['a'].block.stat(blorbKey) + .then(res => { + expect(res).to.have.property('Key') + expect(res).to.have.property('Size') + }) + }) + }) }) diff --git a/test/api/cat.spec.js b/test/api/cat.spec.js index 492df0b8e..04cf9bb17 100644 --- a/test/api/cat.spec.js +++ b/test/api/cat.spec.js @@ -46,4 +46,20 @@ describe('.cat', () => { }) }) }) + + describe('promise', () => { + it('cat', done => { + return apiClients['a'].cat('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') + .then(res => { + let buf = '' + res + .on('error', err => { throw err }) + .on('data', data => buf += data) + .on('end', () => { + expect(buf).to.be.equal(testfile.toString()) + done() + }) + }) + }) + }) }) diff --git a/test/api/commands.spec.js b/test/api/commands.spec.js index 7ec3ac712..a76e23c36 100644 --- a/test/api/commands.spec.js +++ b/test/api/commands.spec.js @@ -8,4 +8,13 @@ describe('.commands', () => { done() }) }) + + describe('promise', () => { + it('lists commands', () => { + return apiClients['a'].commands() + .then(res => { + expect(res).to.exist + }) + }) + }) }) diff --git a/test/api/config.spec.js b/test/api/config.spec.js index 773ac7cbe..2241b6e2c 100644 --- a/test/api/config.spec.js +++ b/test/api/config.spec.js @@ -34,4 +34,37 @@ describe('.config', () => { done() }) }) + + describe('promise', () => { + it('.config.{set, get}', () => { + const confKey = 'arbitraryKey' + const confVal = 'arbitraryVal' + + return apiClients['a'].config.set(confKey, confVal) + .then(res => { + return apiClients['a'].config.get(confKey) + }) + .then(res => { + expect(res).to.have.a.property('Value', confVal) + }) + }) + + it('.config.show', () => { + return apiClients['c'].config.show() + .then(res => { + expect(res).to.exist + }) + }) + + it('.config.replace', () => { + if (!isNode) { + return + } + + return apiClients['c'].config.replace(__dirname + '/../r-config.json') + .then(res => { + expect(res).to.be.equal(null) + }) + }) + }) }) diff --git a/test/api/dht.spec.js b/test/api/dht.spec.js index 25bae8fec..ff6e7c5d3 100644 --- a/test/api/dht.spec.js +++ b/test/api/dht.spec.js @@ -36,4 +36,27 @@ describe('.dht', () => { done() }) }) + + describe('promise', () => { + it('returns an error when getting a non-existent key from the DHT', () => { + return apiClients['a'].dht.get('non-existent', {timeout: '100ms'}) + .catch(err => { + expect(err).to.be.an.instanceof(Error) + }) + }) + + it('puts a key value pair in the DHT', () => { + return apiClients['a'].dht.put('scope', 'interplanetary') + .then(res => { + expect(res).to.be.an('array') + }) + }) + + it('.dht.findprovs', () => { + return apiClients['a'].dht.findprovs('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') + .then(res => { + expect(res).to.be.an('array') + }) + }) + }) }) diff --git a/test/api/diag.spec.js b/test/api/diag.spec.js index 71518401c..e598c7d7f 100644 --- a/test/api/diag.spec.js +++ b/test/api/diag.spec.js @@ -18,4 +18,22 @@ describe('.diag', () => { done() }) }) + + describe('promise', () => { + it('.diag.net', () => { + return apiClients['a'].diag.net() + .then(res => { + expect(res).to.exist + }) + }) + + it('.diag.sys', () => { + return apiClients['a'].diag.sys() + .then(res => { + expect(res).to.exist + expect(res).to.have.a.property('memory') + expect(res).to.have.a.property('diskinfo') + }) + }) + }) }) diff --git a/test/api/id.spec.js b/test/api/id.spec.js index 50d55f0fc..c32b459e2 100644 --- a/test/api/id.spec.js +++ b/test/api/id.spec.js @@ -9,4 +9,14 @@ describe('.id', () => { done() }) }) + + describe('promise', () => { + it('id', () => { + return apiClients['a'].id() + .then(res => { + expect(res).to.have.a.property('ID') + expect(res).to.have.a.property('PublicKey') + }) + }) + }) }) diff --git a/test/api/log.spec.js b/test/api/log.spec.js index 651b9a5b2..37c2f0d9d 100644 --- a/test/api/log.spec.js +++ b/test/api/log.spec.js @@ -13,4 +13,16 @@ describe('.log', () => { }) }) }) + + describe('promise', () => { + it('.log.tail', done => { + apiClients['a'].log.tail() + .then(res => { + res.once('data', obj => { + expect(obj).to.be.an('object') + done() + }) + }) + }) + }) }) diff --git a/test/api/ls.spec.js b/test/api/ls.spec.js index ff823a269..59ce03611 100644 --- a/test/api/ls.spec.js +++ b/test/api/ls.spec.js @@ -33,4 +33,33 @@ describe('ls', function () { done() }) }) + + describe('promise', () => { + it('should correctly retrieve links', () => { + if (!isNode) return + + return apiClients['a'].ls('QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg') + .then(res => { + expect(res).to.have.a.property('Objects') + expect(res.Objects[0]).to.have.a.property('Links') + expect(res.Objects[0]).to.have.property('Hash', 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg') + }) + }) + + it('should correctly handle a nonexisting hash', () => { + return apiClients['a'].ls('surelynotavalidhashheh?') + .catch(err => { + expect(err).to.exist + }) + }) + + it('should correctly handle a nonexisting path', () => { + if (!isNode) return + + return apiClients['a'].ls('QmTDH2RXGn8XyDAo9YyfbZAUXwL1FCr44YJCN9HBZmL9Gj/folder_that_isnt_there') + .catch(err => { + expect(err).to.exist + }) + }) + }) }) diff --git a/test/api/name.spec.js b/test/api/name.spec.js index 2950ee6b1..a834f96dd 100644 --- a/test/api/name.spec.js +++ b/test/api/name.spec.js @@ -22,4 +22,24 @@ describe('.name', () => { done() }) }) + + describe('promise', () => { + it('.name.publish', () => { + return apiClients['a'].name.publish('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') + .then(res => { + name = res + expect(name).to.exist + }) + }) + + it('.name.resolve', () => { + return apiClients['a'].name.resolve(name.Name) + .then(res => { + expect(res).to.exist + expect(res).to.be.eql({ + Path: '/ipfs/' + name.Value + }) + }) + }) + }) }) diff --git a/test/api/object.spec.js b/test/api/object.spec.js index a2288f65b..dca7abdb9 100644 --- a/test/api/object.spec.js +++ b/test/api/object.spec.js @@ -101,4 +101,95 @@ describe('.object', () => { done() }) }) + + describe('promise', () => { + it('object.put', () => { + return apiClients['a'].object.put(testObject, 'json') + .then(res => { + expect(res).to.have.a.property('Hash', testObjectHash) + expect(res.Links).to.be.empty + }) + }) + + it('object.get', () => { + return apiClients['a'].object.get(testObjectHash) + .then(res => { + expect(res).to.have.a.property('Data', 'testdata') + expect(res.Links).to.be.empty + }) + }) + + it('object.data', done => { + return apiClients['a'].object.data(testObjectHash) + .then(res => { + let buf = '' + res + .on('error', err => { throw err }) + .on('data', data => buf += data) + .on('end', () => { + expect(buf).to.equal('testdata') + done() + }) + }) + }) + + it('object.stat', () => { + return apiClients['a'].object.stat(testObjectHash) + .then(res => { + expect(res).to.be.eql({ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD', + NumLinks: 0, + BlockSize: 10, + LinksSize: 2, + DataSize: 8, + CumulativeSize: 10 + }) + }) + }) + + it('object.links', () => { + return apiClients['a'].object.links(testObjectHash) + .then(res => { + expect(res).to.be.eql({ + Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD', + Links: [] + }) + }) + }) + + it('object.patch', () => { + return apiClients['a'].object.put(testPatchObject, 'json') + .then(res => { + return apiClients['a'].object + .patch(testObjectHash, ['add-link', 'next', testPatchObjectHash]) + }) + .then(res => { + expect(res).to.be.eql({ + Hash: 'QmZFdJ3CQsY4kkyQtjoUo8oAzsEs5BNguxBhp8sjQMpgkd', + Links: null + }) + return apiClients['a'].object.get(res.Hash) + }) + .then(res => { + expect(res).to.be.eql({ + Data: 'testdata', + Links: [{ + Name: 'next', + Hash: 'QmWJDtdQWQSajQPx1UVAGWKaSGrHVWdjnrNhbooHP7LuF2', + Size: 15 + }] + }) + }) + }) + + it('object.new', () => { + return apiClients['a'].object.new('unixfs-dir') + .then(res => { + expect(res).to.deep.equal({ + Hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + Links: null + }) + }) + }) + }) }) diff --git a/test/api/pin.spec.js b/test/api/pin.spec.js index 85652f33d..f018fdfce 100644 --- a/test/api/pin.spec.js +++ b/test/api/pin.spec.js @@ -29,4 +29,34 @@ describe('.pin', () => { }) }) }) + + describe('promise', () => { + it('.pin.add', () => { + return apiClients['b'].pin + .add('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {recursive: false}) + .then(res => { + expect(res.Pinned[0]).to.be.equal('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') + }) + }) + + it('.pin.list', () => { + return apiClients['b'].pin.list() + .then(res => { + expect(res).to.exist + }) + }) + + it('.pin.remove', () => { + return apiClients['b'].pin + .remove('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {recursive: false}) + .then(res => { + expect(res).to.exist + return apiClients['b'].pin.list('direct') + }) + .then(res => { + expect(res).to.exist + expect(res.Keys).to.be.empty + }) + }) + }) }) diff --git a/test/api/ping.spec.js b/test/api/ping.spec.js index 4763be0de..6d409216b 100644 --- a/test/api/ping.spec.js +++ b/test/api/ping.spec.js @@ -12,4 +12,16 @@ describe('.ping', () => { }) }) }) + + describe('promise', () => { + it('ping another peer', () => { + return apiClients['b'].id() + .then(id => { + return apiClients['a'].ping(id.ID) + }) + .then(res => { + expect(res).to.have.a.property('Success') + }) + }) + }) }) diff --git a/test/api/refs.spec.js b/test/api/refs.spec.js index 9bbcb0d41..07ad6276c 100644 --- a/test/api/refs.spec.js +++ b/test/api/refs.spec.js @@ -2,6 +2,28 @@ describe('.refs', () => { const folder = 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg' + const result = [{ + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmcUYKmQxmTcFom4R4UZP7FWeQzgJkwcFn51XrvsMy7PE9 add.js', + Err: '' + }, { + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmNeHxDfQfjVFyYj2iruvysLH9zpp78v3cu1s3BZq1j5hY cat.js', + Err: '' + }, { + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmTYFLz5vsdMpq4XXw1a1pSxujJc9Z5V3Aw1Qg64d849Zy files', + Err: '' + }, { + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmU7wetVaAqc3Meurif9hcYBHGvQmL5QdpPJYBoZizyTNL ipfs-add.js', + Err: '' + }, { + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmctZfSuegbi2TMFY2y3VQjxsH5JbRBu7XmiLfHNvshhio ls.js', + Err: '' + }, { + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmTDH2RXGn8XyDAo9YyfbZAUXwL1FCr44YJCN9HBZmL9Gj test-folder', + Err: '' + }, { + Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmbkMNB6rwfYAxRvnG9CWJ6cKKHEdq2ZKTozyF5FQ7H8Rs version.js', + Err: '' + }] it('refs', done => { if (!isNode) { @@ -11,31 +33,20 @@ describe('.refs', () => { apiClients['a'].refs(folder, {'format': ' '}, (err, objs) => { expect(err).to.not.exist - const result = [{ - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmcUYKmQxmTcFom4R4UZP7FWeQzgJkwcFn51XrvsMy7PE9 add.js', - Err: '' - }, { - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmNeHxDfQfjVFyYj2iruvysLH9zpp78v3cu1s3BZq1j5hY cat.js', - Err: '' - }, { - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmTYFLz5vsdMpq4XXw1a1pSxujJc9Z5V3Aw1Qg64d849Zy files', - Err: '' - }, { - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmU7wetVaAqc3Meurif9hcYBHGvQmL5QdpPJYBoZizyTNL ipfs-add.js', - Err: '' - }, { - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmctZfSuegbi2TMFY2y3VQjxsH5JbRBu7XmiLfHNvshhio ls.js', - Err: '' - }, { - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmTDH2RXGn8XyDAo9YyfbZAUXwL1FCr44YJCN9HBZmL9Gj test-folder', - Err: '' - }, { - Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmbkMNB6rwfYAxRvnG9CWJ6cKKHEdq2ZKTozyF5FQ7H8Rs version.js', - Err: '' - }] expect(objs).to.eql(result) done() }) }) + + describe('promise', () => { + it('refs', () => { + if (!isNode) return + + return apiClients['a'].refs(folder, {'format': ' '}) + .then(objs => { + expect(objs).to.eql(result) + }) + }) + }) }) diff --git a/test/api/swarm.spec.js b/test/api/swarm.spec.js index 198f82ef6..ea5595e42 100644 --- a/test/api/swarm.spec.js +++ b/test/api/swarm.spec.js @@ -9,8 +9,18 @@ describe('.swarm', () => { done() }) }) + it('.swarm.connect', done => { // Done in the 'before' segment done() }) + + describe('promise', () => { + it('.swarm.peers', () => { + return apiClients['a'].swarm.peers() + .then(res => { + expect(res.Strings).to.have.length.above(1) + }) + }) + }) }) diff --git a/test/api/version.spec.js b/test/api/version.spec.js index 4eadddbb3..34f688a65 100644 --- a/test/api/version.spec.js +++ b/test/api/version.spec.js @@ -42,4 +42,24 @@ describe('.version', () => { done() }) }) + + describe('promise', () => { + it('checks the version', () => { + return apiClients['a'].version() + .then(res => { + expect(res).to.have.a.property('Version') + expect(res).to.have.a.property('Commit') + expect(res).to.have.a.property('Repo') + }) + }) + + it('with number option', () => { + return apiClients['a'].version({number: true}) + .then(res => { + expect(res).to.have.a.property('Version') + expect(res).to.have.a.property('Commit') + expect(res).to.have.a.property('Repo') + }) + }) + }) })