diff --git a/package.json b/package.json index 84b97297..2a2b84e3 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,14 @@ "bl": "^1.1.2", "chai": "^3.5.0", "pre-commit": "^1.1.3", + "run-series": "^1.1.4", "webrtcsupport": "^2.2.0" }, "dependencies": { "debug": "^2.2.0", "duplexify": "^3.4.3", "hapi": "^13.4.1", + "interface-connection": "^0.1.3", "mafmt": "^2.1.0", "minimist": "^1.2.0", "peer-id": "^0.7.0", @@ -61,4 +63,4 @@ "David Dias ", "dignifiedquire " ] -} \ No newline at end of file +} diff --git a/src/webrtc-star/index.js b/src/webrtc-star/index.js index a47b69ff..137d01b8 100644 --- a/src/webrtc-star/index.js +++ b/src/webrtc-star/index.js @@ -4,135 +4,161 @@ const debug = require('debug') const log = debug('libp2p:webrtc-star') const multiaddr = require('multiaddr') const mafmt = require('mafmt') -const parallel = require('run-parallel') const io = require('socket.io-client') const EE = require('events').EventEmitter const SimplePeer = require('simple-peer') -const Duplexify = require('duplexify') const peerId = require('peer-id') const PeerInfo = require('peer-info') +const Connection = require('interface-connection').Connection exports = module.exports = WebRTCStar +const sioOptions = { + transports: ['websocket'], + 'force new connection': true +} + function WebRTCStar () { if (!(this instanceof WebRTCStar)) { return new WebRTCStar() } - const listeners = [] - let mhSelf + let maSelf + const listeners = {} this.discovery = new EE() - this.dial = function (multiaddr, options) { - if (!options) { + this.dial = function (ma, options, callback) { + if (typeof options === 'function') { + callback = options options = {} } - options.ready = options.ready || function noop () {} - const pt = new Duplexify() + if (!callback) { + callback = function noop () {} + } + + const conn = new Connection() const intentId = (~~(Math.random() * 1e9)).toString(36) + Date.now() - const sioClient = listeners[0] - const conn = new SimplePeer({ initiator: true, trickle: false }) + const sioClient = listeners[Object.keys(listeners)[0]].io + const channel = new SimplePeer({ initiator: true, trickle: false }) - conn.on('signal', function (signal) { + channel.on('signal', function (signal) { sioClient.emit('ss-handshake', { intentId: intentId, - srcMultiaddr: mhSelf.toString(), - dstMultiaddr: multiaddr.toString(), + srcMultiaddr: maSelf.toString(), + dstMultiaddr: ma.toString(), signal: signal }) }) + channel.on('timeout', () => { + conn.emit('timeout') + }) + + channel.on('error', (err) => { + callback(err) + conn.emit('error', err) + }) + sioClient.on('ws-handshake', (offer) => { if (offer.intentId !== intentId || !offer.answer) { return } - conn.on('connect', () => { - pt.setReadable(conn) - pt.setWritable(conn) + channel.on('connect', () => { + conn.setInnerConn(channel) - pt.destroy = conn.destroy.bind(conn) + conn.destroy = channel.destroy.bind(channel) - conn.on('close', () => { - pt.emit('close') + channel.on('close', () => { + conn.emit('close') }) - pt.getObservedAddrs = () => { - return [multiaddr] + conn.getObservedAddrs = () => { + return [ma] } - options.ready(null, pt) + + conn.emit('connect') + callback(null, conn) }) - conn.signal(offer.signal) + + channel.signal(offer.signal) }) - return pt + return conn } - this.createListener = (multiaddrs, handler, callback) => { - if (!Array.isArray(multiaddrs)) { - multiaddrs = [multiaddrs] + this.createListener = (options, handler) => { + if (typeof options === 'function') { + handler = options + options = {} } - const sioOptions = { - transports: ['websocket'], - 'force new connection': true - } - // for now it only supports listening in one signalling server - // no technical limitation why not to do more :) - const mh = multiaddrs[0] - mhSelf = mh - // I know.. "websockets connects on a http endpoint, but through a - // tcp port" - const sioUrl = 'http://' + mh.toString().split('/')[3] + ':' + mh.toString().split('/')[5] - const sioClient = io.connect(sioUrl, sioOptions) - sioClient.on('connect_error', callback) - sioClient.on('connect', () => { - sioClient.emit('ss-join', multiaddrs[0].toString()) - sioClient.on('ws-handshake', incommingDial) - sioClient.on('ws-peer', peerDiscovered.bind(this)) - listeners.push(sioClient) - callback() - }) + const listener = new EE() - function incommingDial (offer) { - if (offer.answer) { - return + listener.listen = (ma, callback) => { + if (!callback) { + callback = function noop () {} } + maSelf = ma + + const sioUrl = 'http://' + ma.toString().split('/')[3] + ':' + ma.toString().split('/')[5] + + listener.io = io.connect(sioUrl, sioOptions) + listener.io.on('connect_error', callback) + listener.io.on('connect', () => { + listener.io.emit('ss-join', ma.toString()) + listener.io.on('ws-handshake', incommingDial) + listener.io.on('ws-peer', peerDiscovered.bind(this)) + listener.emit('listening') + callback() + }) - const conn = new SimplePeer({ trickle: false }) + function incommingDial (offer) { + if (offer.answer) { return } - conn.on('connect', () => { - conn.getObservedAddrs = () => { - return [] - } + const channel = new SimplePeer({ trickle: false }) + const conn = Connection(channel) - handler(conn) - }) + channel.on('connect', () => { + conn.getObservedAddrs = () => { + return [offer.srcMultiaddr] + } - conn.on('signal', function (signal) { - offer.signal = signal - offer.answer = true - sioClient.emit('ss-handshake', offer) - }) + listener.emit('connection', conn) + handler(conn) + }) - conn.signal(offer.signal) - } - } + channel.on('signal', (signal) => { + offer.signal = signal + offer.answer = true + listener.io.emit('ss-handshake', offer) + }) - this.close = (callback) => { - if (listeners.length === 0) { - log('Called close with no active listeners') - return callback() + channel.signal(offer.signal) + } } - parallel(listeners.map((listener) => { - return (cb) => { - listener.emit('ss-leave') - cb() + listener.close = (callback) => { + if (!callback) { + callback = function noop () {} } - }), callback) + listener.io.emit('ss-leave') + setTimeout(() => { + listener.emit('close') + callback() + }, 100) + } + + listener.getAddrs = (callback) => { + process.nextTick(() => { + callback(null, [maSelf]) + }) + } + + listeners[multiaddr.toString()] = listener + return listener } this.filter = (multiaddrs) => { @@ -144,10 +170,11 @@ function WebRTCStar () { }) } - function peerDiscovered (mh) { - const id = peerId.createFromB58String(mh.split('/')[8]) + function peerDiscovered (maStr) { + log('Peer Discovered:', maStr) + const id = peerId.createFromB58String(maStr.split('/')[8]) const peer = new PeerInfo(id) - peer.multiaddr.add(multiaddr(mh)) + peer.multiaddr.add(multiaddr(maStr)) this.discovery.emit('peer', peer) } } diff --git a/test/browser.js b/test/browser.js index 13bbe93b..5371f0b4 100644 --- a/test/browser.js +++ b/test/browser.js @@ -2,11 +2,12 @@ const webrtcSupport = require('webrtcsupport') +require('./webrtc-star/test-instance.js') require('./webrtc-star/test-filter.js') -require('./webrtc-star/test-join-and-leave.js') +require('./webrtc-star/test-listen.js') if (webrtcSupport.support) { - require('./webrtc-star/test-dial-and-listen.js') - require('./webrtc-star/test-dial-and-destroy.js') + require('./webrtc-star/test-dial.js') require('./webrtc-star/test-discovery.js') + require('./webrtc-star/test-valid-connection.js') } diff --git a/test/webrtc-star/test-dial-and-destroy.js b/test/webrtc-star/test-dial-and-destroy.js deleted file mode 100644 index cc014997..00000000 --- a/test/webrtc-star/test-dial-and-destroy.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const expect = require('chai').expect -const multiaddr = require('multiaddr') -const parallel = require('run-parallel') - -const WebRTCStar = require('../../src/webrtc-star') - -describe('dial and destroy', () => { - let ws1 - const mh1 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooon') - - let ws2 - const mh2 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooS') - - let connCatcher = (conn) => { - conn.pipe(conn) - } - - it('listen on the first', (done) => { - ws1 = new WebRTCStar() - - ws1.createListener(mh1, (conn) => { - connCatcher(conn) - }, (err) => { - expect(err).to.not.exist - done() - }) - }) - - it('listen on the second', (done) => { - ws2 = new WebRTCStar() - - ws2.createListener(mh2, (conn) => { - connCatcher(conn) - }, (err) => { - expect(err).to.not.exist - done() - }) - }) - - it('dial and destroy from dialer', (done) => { - connCatcher = (conn) => { - conn.on('close', done) - } - - ws1.dial(mh2, { ready: (err, conn) => { - expect(err).to.not.exist - // When things are all in the same thread.. - setTimeout(conn.destroy, 100) - }}) - }) - - it('dial and destroy from listener', (done) => { - connCatcher = (conn) => { - conn.destroy() - } - - ws1.dial(mh2, { ready: (err, conn) => { - expect(err).to.not.exist - conn.on('close', done) - }}) - }) - - it('close', (done) => { - parallel([ - ws1.close, - ws2.close - ], done) - }) -}) diff --git a/test/webrtc-star/test-dial-and-listen.js b/test/webrtc-star/test-dial-and-listen.js deleted file mode 100644 index f1875e6c..00000000 --- a/test/webrtc-star/test-dial-and-listen.js +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const expect = require('chai').expect -const multiaddr = require('multiaddr') -const parallel = require('run-parallel') -const bl = require('bl') - -const WebRTCStar = require('../../src/webrtc-star') - -describe('listen and dial', () => { - let ws1 - const mh1 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooA') - - let ws2 - const mh2 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooB') - - it('listen on the first', (done) => { - ws1 = new WebRTCStar() - - ws1.createListener(mh1, (conn) => { - conn.pipe(conn) - }, (err) => { - expect(err).to.not.exist - done() - }) - }) - - it('listen on the second', (done) => { - ws2 = new WebRTCStar() - - ws2.createListener(mh2, (conn) => { - conn.pipe(conn) - }, (err) => { - expect(err).to.not.exist - done() - }) - }) - - it('dial', (done) => { - const conn = ws1.dial(mh2) - const text = 'Hello World' - conn.pipe(bl((err, data) => { - expect(err).to.not.exist - expect(data.toString()).to.equal(text) - done() - })) - - conn.write(text) - conn.end() - }) - - it('close', (done) => { - parallel([ - ws1.close, - ws2.close - ], done) - }) -}) diff --git a/test/webrtc-star/test-dial.js b/test/webrtc-star/test-dial.js new file mode 100644 index 00000000..3b42e19c --- /dev/null +++ b/test/webrtc-star/test-dial.js @@ -0,0 +1,59 @@ +/* eslint-env mocha */ + +'use strict' + +const expect = require('chai').expect +const multiaddr = require('multiaddr') +const series = require('run-series') + +const WebRTCStar = require('../../src/webrtc-star') + +describe('dial', () => { + let ws1 + const ma1 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2a') + + let ws2 + const ma2 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo2b') + + before((done) => { + series([ + first, + second + ], done) + + function first (next) { + ws1 = new WebRTCStar() + + const listener = ws1.createListener((conn) => { + conn.pipe(conn) + }) + + listener.listen(ma1, next) + } + + function second (next) { + ws2 = new WebRTCStar() + + const listener = ws2.createListener((conn) => { + conn.pipe(conn) + }) + listener.listen(ma2, next) + } + }) + + it('dial on IPv4, check callback', (done) => { + ws1.dial(ma2, (err, conn) => { + expect(err).to.not.exist + done() + }) + }) + + it('dial on IPv4, check for connect event', (done) => { + const conn = ws1.dial(ma2) + conn.on('connect', done) + }) + + it.skip('dial on IPv6', (done) => { + // TODO IPv6 not supported yet + }) +}) diff --git a/test/webrtc-star/test-discovery.js b/test/webrtc-star/test-discovery.js index 45330367..7eac3e3b 100644 --- a/test/webrtc-star/test-discovery.js +++ b/test/webrtc-star/test-discovery.js @@ -9,15 +9,16 @@ const WebRTCStar = require('../../src/webrtc-star') describe('peer discovery', () => { let ws1 - const mh1 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooA') + const ma1 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo3A') let ws2 - const mh2 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooB') + const ma2 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo3B') it('listen on the first', (done) => { ws1 = new WebRTCStar() - ws1.createListener(mh1, (conn) => {}, (err) => { + const listener = ws1.createListener((conn) => {}) + listener.listen(ma1, (err) => { expect(err).to.not.exist done() }) @@ -27,11 +28,12 @@ describe('peer discovery', () => { ws2 = new WebRTCStar() ws1.discovery.on('peer', (peerInfo) => { - expect(peerInfo.multiaddrs[0]).to.deep.equal(mh2) + expect(peerInfo.multiaddrs[0]).to.deep.equal(ma2) done() }) - ws2.createListener(mh2, (conn) => {}, (err) => { + const listener = ws2.createListener((conn) => {}) + listener.listen(ma2, (err) => { expect(err).to.not.exist }) }) diff --git a/test/webrtc-star/test-filter.js b/test/webrtc-star/test-filter.js index 67690644..35cffbf7 100644 --- a/test/webrtc-star/test-filter.js +++ b/test/webrtc-star/test-filter.js @@ -9,8 +9,9 @@ const WebRTCStar = require('../../src/webrtc-star') describe('filter', () => { it('filters non valid webrtc-star multiaddrs', () => { const ws = new WebRTCStar() - const mhArr = [ + const maArr = [ multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo1'), + multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws'), multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo2'), multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo3'), multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4'), @@ -18,7 +19,15 @@ describe('filter', () => { multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') ] - const filtered = ws.filter(mhArr) + const filtered = ws.filter(maArr) expect(filtered.length).to.equal(4) }) + + it('filter a single addr for this transport', () => { + const ws = new WebRTCStar() + const ma = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo1') + + const filtered = ws.filter(ma) + expect(filtered.length).to.equal(1) + }) }) diff --git a/test/webrtc-star/test-instance.js b/test/webrtc-star/test-instance.js new file mode 100644 index 00000000..8464e3a2 --- /dev/null +++ b/test/webrtc-star/test-instance.js @@ -0,0 +1,19 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const WebRTCStar = require('./../../src') + +describe('instantiate the transport', () => { + it('create', (done) => { + const wstar = new WebRTCStar() + expect(wstar).to.exist + done() + }) + + it('create without new', (done) => { + const wstar = WebRTCStar() + expect(wstar).to.exist + done() + }) +}) diff --git a/test/webrtc-star/test-join-and-leave.js b/test/webrtc-star/test-join-and-leave.js deleted file mode 100644 index 251f1b49..00000000 --- a/test/webrtc-star/test-join-and-leave.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const expect = require('chai').expect -const multiaddr = require('multiaddr') - -const WebRTCStar = require('../../src/webrtc-star') - -describe('join and leave the signalling server', () => { - let ws - - it('listen', (done) => { - ws = new WebRTCStar() - const mh = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooA') - - ws.createListener(mh, (conn) => {}, (err) => { - expect(err).to.not.exist - done() - }) - }) - - it('close', (done) => { - ws.close(done) - }) -}) diff --git a/test/webrtc-star/test-listen.js b/test/webrtc-star/test-listen.js new file mode 100644 index 00000000..c8d0022b --- /dev/null +++ b/test/webrtc-star/test-listen.js @@ -0,0 +1,62 @@ +/* eslint-env mocha */ + +'use strict' + +const expect = require('chai').expect +const multiaddr = require('multiaddr') +const WebRTCStar = require('../../src/webrtc-star') + +describe('listen', () => { + let ws + + const ma = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooooA') + + beforeEach(() => { + ws = new WebRTCStar() + }) + + it('listen, check for callback', (done) => { + const listener = ws.createListener((conn) => {}) + listener.listen(ma, (err) => { + expect(err).to.not.exist + listener.close(done) + }) + }) + + it('listen, check for listening event', (done) => { + const listener = ws.createListener((conn) => {}) + listener.on('listening', () => { + listener.close(done) + }) + listener.listen(ma) + }) + + it('listen, check for the close event', (done) => { + const listener = ws.createListener((conn) => {}) + listener.listen(ma, (err) => { + expect(err).to.not.exist + listener.on('close', done) + listener.close() + }) + }) + + it.skip('close listener with connections, through timeout', (done) => { + // TODO ? Should this apply ? + }) + + it.skip('listen on IPv6 addr', (done) => { + // TODO IPv6 not supported yet + }) + + it('getAddrs', (done) => { + const listener = ws.createListener((conn) => {}) + listener.listen(ma, (err) => { + expect(err).to.not.exist + listener.getAddrs((err, addrs) => { + expect(err).to.not.exist + expect(addrs[0]).to.deep.equal(ma) + listener.close(done) + }) + }) + }) +}) diff --git a/test/webrtc-star/test-valid-connection.js b/test/webrtc-star/test-valid-connection.js new file mode 100644 index 00000000..3d1bdcd3 --- /dev/null +++ b/test/webrtc-star/test-valid-connection.js @@ -0,0 +1,73 @@ +/* eslint-env mocha */ +'use strict' + +const expect = require('chai').expect +const multiaddr = require('multiaddr') +const series = require('run-series') + +const WebRTCStar = require('../../src/webrtc-star') + +describe.skip('valid Connection', () => { + let ws1 + const ma1 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo5a') + + let ws2 + const ma2 = multiaddr('/libp2p-webrtc-star/ip4/127.0.0.1/tcp/15555/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSooo5b') + + let conn + + before((done) => { + series([ + first, + second + ], dial) + + function first (next) { + ws1 = new WebRTCStar() + + const listener = ws1.createListener((conn) => { + conn.pipe(conn) + }) + + listener.listen(ma1, next) + } + + function second (next) { + ws2 = new WebRTCStar() + + const listener = ws2.createListener((conn) => { + conn.pipe(conn) + }) + listener.listen(ma2, next) + } + + function dial () { + conn = ws1.dial(ma2) + conn.on('connect', done) + } + }) + + it('get observed addrs', (done) => { + conn.getObservedAddrs((err, addrs) => { + expect(err).to.not.exist + expect(addrs[0]).to.deep.equal(ma2) + done() + }) + }) + it('get Peer Info', (done) => { + conn.getPeerInfo((err, peerInfo) => { + expect(err).to.exist + done() + }) + }) + + it('set Peer Info', (done) => { + conn.setPeerInfo('info') + conn.getPeerInfo((err, peerInfo) => { + expect(err).to.not.exist + expect(peerInfo).to.equal('info') + done() + }) + }) +}) +