From 84ad9eb2c22b87eda7fa0c9aff1be4fbf06597b5 Mon Sep 17 00:00:00 2001 From: Kagami Hiiragi Date: Wed, 25 Feb 2015 13:28:58 +0300 Subject: [PATCH] Extract transports to bitmessage-transports Fixes #13 --- README.md | 8 +- karma.conf.js | 10 +- lib/net/base.js | 182 ---------------------- lib/net/tcp.js | 324 --------------------------------------- lib/net/ws.browser.js | 125 --------------- lib/net/ws.js | 247 ----------------------------- package.json | 16 +- tests/unit.js => test.js | 4 +- tests/functional.js | 157 ------------------- tests/index.js | 17 -- tests/run-test-nodes.js | 43 ------ tests/tcp-node.js | 17 -- tests/ws-node.js | 15 -- 13 files changed, 12 insertions(+), 1153 deletions(-) delete mode 100644 lib/net/base.js delete mode 100644 lib/net/tcp.js delete mode 100644 lib/net/ws.browser.js delete mode 100644 lib/net/ws.js rename tests/unit.js => test.js (99%) delete mode 100644 tests/functional.js delete mode 100644 tests/index.js delete mode 100644 tests/run-test-nodes.js delete mode 100644 tests/tcp-node.js delete mode 100644 tests/ws-node.js diff --git a/README.md b/README.md index e8b4594..1213800 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,6 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/ - [x] High-level classes - [x] Address - [x] UserAgent -- [ ] Network transports - - [x] TCP (Node.js only) - - [x] WebSocket - - [ ] WebRTC - [ ] PyBitmessage configs parsing - [ ] keys.dat - [ ] knownnodes.dat @@ -129,9 +125,11 @@ console.log(structs.message.decode(verackmsg).command); // verack ### Networking +You will need to install [bitmessage-transports](https://github.com/bitchan/bitmessage-transports) library. + ```js var messages = require("bitmessage").messages; -var TcpTransport = require("bitmessage/lib/net/tcp"); +var TcpTransport = require("bitmessage-transports").TcpTransport; var tcp = new TcpTransport({ dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]], diff --git a/karma.conf.js b/karma.conf.js index c5aba59..6c2dec6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,6 +1,5 @@ process.env.CHROME_BIN = "chromium-browser"; var allTests = process.env.ALL_TESTS === "1"; -if (process.env.TEST_MODE !== "unit") require("./tests/run-test-nodes.js")(); module.exports = function(config) { config.set({ @@ -15,20 +14,17 @@ module.exports = function(config) { // list of files / patterns to load in the browser - files: ["tests/index.js"], + files: ["test.js"], // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - "tests/index.js": ["browserify", "env"], + "test.js": ["browserify", "env"], }, - envPreprocessor: [ - "ALL_TESTS", - "TEST_MODE", - ], + envPreprocessor: ["ALL_TESTS"], // list of files to exclude diff --git a/lib/net/base.js b/lib/net/base.js deleted file mode 100644 index c86c945..0000000 --- a/lib/net/base.js +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Networking base module. Defines base transport interface, useful for - * implementing new transports. End-users should import some transport - * instead in order to connect/accept connections to/from other nodes. - * **NOTE**: `BaseTransport` is exported as a module. - * @example var BaseTransport = require("bitmessage/lib/net/base"); - * @module bitmessage/net/base - */ -// TODO(Kagami): Write some sort of tutorial. - -"use strict"; - -var inherits = require("inherits"); -var EventEmitter = require("events").EventEmitter; -var bufferEqual = require("buffer-equal"); -var util = require("../_util"); -var PPromise = require("../platform").Promise; -var structs = require("../structs"); -var messages = require("../messages"); - -var ServicesBitfield = structs.ServicesBitfield; - -/** - * Base transport class. Allows to use single class for both client and - * server modes (as separate instances). - * @constructor - * @static - */ -function BaseTransport() { - BaseTransport.super_.call(this); -} - -inherits(BaseTransport, EventEmitter); - -/** - * Do the transport-specific bootstrap process and return promise that - * contains discovered nodes when fulfilled (both modes). - * NOTE: Do not use nodes received by this method in `addr` messages! - * This is meaningless. - * @return {Promise.} - * @abstract - */ -BaseTransport.prototype.bootstrap = function() { - return PPromise.reject(new Error("Not implemented")); -}; - -/** - * Connect to the transport-specific address. Enters client mode. Should - * emit `open` event after successful connect and `established` event - * after `verack` messages exchange. - * @abstract - */ -BaseTransport.prototype.connect = function() { - throw new Error("Not implemented"); -}; - -/** - * Listen for the transport-specific incoming connections. Enters server - * mode. Should emit `connection` event with a transport instance for - * each new connection. - * @abstract - */ -BaseTransport.prototype.listen = function() { - throw new Error("Not implemented"); -}; - -/** - * Send [message]{@link module:bitmessage/structs.message} over the - * wire (client mode only). - * @param {(Buffer|string)} msg - Encoded message or command string - * @param {Buffer=} payload - Message payload (used if the first - * argument is a string) - * @abstract - */ -BaseTransport.prototype.send = function() { - throw new Error("Not implemented"); -}; - -/** - * Send [message]{@link module:bitmessage/structs.message} to all - * connected clients (server mode only). - * @param {(Buffer|string)} msg - Encoded message or command string - * @param {Buffer=} payload - Message payload (used if the first - * argument is a string) - * @abstract - */ -BaseTransport.prototype.broadcast = function() { - throw new Error("Not implemented"); -}; - -/** - * Close connection(s) and/or stop listening (both modes). - * @abstract - */ -BaseTransport.prototype.close = function() { - throw new Error("Not implemented"); -}; - -// Private helpers. - -// Make a message from variable number of arguments. -BaseTransport._getmsg = function(args) { - if (typeof args[0] === "string") { - return structs.message.encode(args[0], args[1]); - } else { - return args[0]; - } -}; - -// Unmap IPv4-mapped IPv6 address. -BaseTransport._unmap = function(addr) { - if (addr.slice(0, 7) === "::ffff:") { - return addr.slice(7); - } else { - return addr; - } -}; - -// Check whether two given arrays intersect. -// NOTE(Kagami): It has O(n*m) complexity in the worst case but: -// * Max length of stream list = 160,000 -// * One of the arrays (our streams) should have reasonable length -function intersects(a, b) { - var alen = a.length; - var blen = b.length; - if (!alen || !blen) { - return false; - } - var i, j; - for (i = 0; i < alen; ++i) { - for (j = 0; j < blen; ++j) { - if (a[i] === b[j]) { - return true; - } - } - } - return false; -} - -// Decode and validate version message. -BaseTransport.prototype._decodeVersion = function(payload, opts) { - opts = opts || {}; - var version; - try { - version = messages.version.decodePayload(payload); - } catch(err) { - throw new Error("Version decode error: " + err.message); - } - if (version.version < util.PROTOCOL_VERSION) { - throw new Error("Peer uses old protocol v" + version.version); - } - // TODO(Kagami): We may want to send error message describing the time - // offset problem to this node as PyBitmessage. - var delta = (version.time.getTime() - new Date().getTime()) / 1000; - if (delta > 3600) { - throw new Error("Peer's time is too far in the future: +" + delta + "s"); - } - if (delta < -3600) { - throw new Error("Peer's time is too far in the past: " + delta + "s"); - } - if (bufferEqual(version.nonce, messages.version.randomNonce)) { - throw new Error("Connection to self"); - } - if (!intersects(this.streams, version.streams)) { - throw new Error( - "Peer isn't interested in our streams; " + - "first 10 peer's streams: " + version.streams.slice(0, 10) - ); - } - if (opts.network && !version.services.get(ServicesBitfield.NODE_NETWORK)) { - throw new Error("Not a normal network node: " + version.services); - } - if (opts.gateway && !version.services.get(ServicesBitfield.NODE_GATEWAY)) { - throw new Error("Not a gateway node: " + version.services); - } - if (opts.mobile && !version.services.get(ServicesBitfield.NODE_MOBILE)) { - throw new Error("Not a mobile node: " + version.services); - } - return version; -}; - -module.exports = BaseTransport; diff --git a/lib/net/tcp.js b/lib/net/tcp.js deleted file mode 100644 index ce02b07..0000000 --- a/lib/net/tcp.js +++ /dev/null @@ -1,324 +0,0 @@ -/** - * TCP transport compatible with PyBitmessage. Available only for Node - * platform. - * **NOTE**: `TcpTransport` is exported as a module. - * @module bitmessage/net/tcp - * @example - * var messages = require("bitmessage").messages; - * var TcpTransport = require("bitmessage/lib/net/tcp"); - * - * var tcp = new TcpTransport({ - * dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]], - * }); - * - * tcp.bootstrap().then(function(nodes) { - * var remoteHost = nodes[0][0]; - * var remotePort = nodes[0][1]; - * console.log("Connecting to", nodes[0]); - * tcp.connect(remotePort, remoteHost); - * }); - * - * tcp.on("established", function(version) { - * console.log("Connection established to", version.userAgent); - * - * tcp.on("message", function(command, payload) { - * console.log("Got new", command, "message"); - * var decoded; - * if (command === "addr") { - * decoded = messages.addr.decodePayload(payload); - * console.log("Got", decoded.addrs.length, "node addresses"); - * } - * }); - * }); - */ - -"use strict"; - -var objectAssign = Object.assign || require("object-assign"); -var inherits = require("inherits"); -var net = require("net"); -var dns = require("dns"); -var util = require("../_util"); -var PPromise = require("../platform").Promise; -var structs = require("../structs"); -var messages = require("../messages"); -var BaseTransport = require("./base"); - -var assert = util.assert; -var getmsg = BaseTransport._getmsg; -var unmap = BaseTransport._unmap; - -/** - * TCP transport class. Implements [base transport interface]{@link - * module:bitmessage/net/base.BaseTransport}. - * @param {Object=} opts - Transport options - * @param {Array} opts.seeds - Bootstrap nodes (none by default) - * @param {Array} opts.dnsSeeds - Bootstrap DNS nodes (none by default) - * @param {Object} opts.services - - * [Service features]{@link module:bitmessage/structs.ServicesBitfield} - * provided by this node (`NODE_NETWORK` by default) - * @param {(Array|string|Buffer)} opts.userAgent - - * [User agent]{@link module:bitmessage/user-agent} of this node - * (user agent of bitmessage library by default) - * @param {number[]} opts.streams - Streams accepted by this node ([1] - * by default) - * @param {number} opts.port - Incoming port of this node (8444 by - * default) - * @constructor - * @static - */ -function TcpTransport(opts) { - TcpTransport.super_.call(this); - objectAssign(this, opts); - this.seeds = this.seeds || []; - this.dnsSeeds = this.dnsSeeds || []; - this.streams = this.streams || [1]; - this._clients = {}; -} - -inherits(TcpTransport, BaseTransport); - -TcpTransport.prototype._sendVersion = function() { - return this.send(messages.version.encode({ - services: this.services, - userAgent: this.userAgent, - streams: this.streams, - port: this.port, - remoteHost: this._client.remoteAddress, - remotePort: this._client.remotePort, - })); -}; - -TcpTransport.prototype._setupClient = function(client, incoming) { - var self = this; - self._client = client; - var cache = Buffer(0); - var decoded; - var verackSent = false; - var verackReceived = false; - var established = false; - - // Set default transport timeout per spec. - // TODO(Kagami): We may also want to close connection if it wasn't - // established within minute. - client.setTimeout(20000); - - client.on("connect", function() { - // NOTE(Kagami): This handler shouldn't be called at all for - // incoming connections but let's be sure. - if (!incoming) { - self.emit("open"); - self._sendVersion(); - } - }); - - client.on("data", function(data) { - // TODO(Kagami): We may want to preallocate 1.6M buffer for each - // client instead (max size of the message) to not constantly - // allocate new buffers. Though this may lead to another issues: too - // many memory per client. - cache = Buffer.concat([cache, data]); - while (true) { - decoded = structs.message.tryDecode(cache); - if (!decoded) { - break; - } - cache = decoded.rest; - if (decoded.message) { - self.emit("message", decoded.message.command, decoded.message.payload); - } else if (decoded.error) { - // TODO(Kagami): Wrap it in custom error class? - // TODO(Kagami): Send `error` message and ban node for some time - // if there were too many errors? - self.emit("warning", new Error( - "Message decoding error: " + decoded.error.message - )); - } - } - }); - - // High-level message processing. - self.on("message", function(command, payload) { - var version; - if (!established) { - if (command === "version") { - if (verackSent) { - return; - } - try { - version = self._decodeVersion(payload, {network: true}); - } catch(err) { - self.emit("error", err); - return client.end(); - } - self.send("verack"); - verackSent = true; - if (incoming) { - self._sendVersion(); - } else if (verackReceived) { - self.emit("established", version); - } - } else if (command === "verack") { - verackReceived = true; - if (verackSent) { - self.emit("established", version); - } - } - } - }); - - self.on("established", function() { - established = true; - // Raise timeout up to 10 minutes per spec. - // TODO(Kagami): Send pong messages every 5 minutes as PyBitmessage. - client.setTimeout(600000); - }); - - client.on("timeout", function() { - client.end(); - }); - - client.on("error", function(err) { - self.emit("error", err); - }); - - client.on("close", function() { - self.emit("close"); - delete self._client; - }); -}; - -function resolveDnsSeed(seed) { - var host = seed[0]; - var port = seed[1]; - var nodes = []; - // NOTE(Kagami): - // 1) Node's `getaddrinfo` (`dns.lookup`) returns only one address so - // we can't use it. - // 2) Node's `dig host any` (`dns.resolve`) doesn't return type of the - // record! So we resolve twice for A and AAAA. - // 3) We ignore any errors here, promise's result is always a list. - return new PPromise(function(resolve) { - dns.resolve4(host, function(err, nodes4) { - if (!err) { - nodes4.forEach(function(n) { - nodes.push([n, port]); - }); - } - dns.resolve6(host, function(err, nodes6) { - if (!err) { - nodes6.forEach(function(n) { - nodes.push([n, port]); - }); - } - resolve(nodes); - }); - }); - }); -} - -TcpTransport.prototype.bootstrap = function() { - var hardcodedNodes = this.seeds; - // FIXME(Kagami): Filter incorrect/private IP range nodes? - // See also: . - return this.bootstrapDns().then(function(dnsNodes) { - // Add hardcoded nodes to the end of list because DNS nodes should - // be more up-to-date. - return dnsNodes.concat(hardcodedNodes); - }); -}; - -/** - * Do only DNS-specific bootstrap. - * @return {Promise.} Discovered seed nodes. - */ -TcpTransport.prototype.bootstrapDns = function() { - var promises = this.dnsSeeds.map(resolveDnsSeed); - return PPromise.all(promises).then(function(dnsNodes) { - // Flatten array of arrays. - return Array.prototype.concat.apply([], dnsNodes); - }); -}; - -/** - * Connect to a TCP node. Connection arguments are the same as for - * [net.connect](http://nodejs.org/api/net.html#net_net_connect_port_host_connectlistener). - */ -TcpTransport.prototype.connect = function() { - assert(!this._client, "Already connected"); - assert(!this._server, "Already listening"); - this._setupClient(net.connect.apply(null, arguments)); -}; - -/** - * Listen for incoming TCP connections. Listen arguments are the same as - * for - * [server.listen](http://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback). - */ -TcpTransport.prototype.listen = function() { - assert(!this._client, "Already connected"); - assert(!this._server, "Already listening"); - - var self = this; - var server = self._server = net.createServer(); - server.listen.apply(server, arguments); - - var clientIdCounter = 0; - - server.on("connection", function(client) { - var id = client.id = clientIdCounter++; - self._clients[id] = client; - client.on("close", function() { - delete self._clients[id]; - }); - var opts = objectAssign({}, self); - delete opts._server; - var transport = new self.constructor(opts); - var incoming = true; - transport._setupClient(client, incoming); - var addr = client.remoteAddress; - var port = client.remotePort; - self.emit("connection", transport, unmap(addr), port); - }); - - server.on("error", function(err) { - self.emit("error", err); - }); - - server.on("close", function() { - self.emit("close"); - delete self._server; - }); -}; - -TcpTransport.prototype.send = function() { - if (this._client) { - this._client.write(getmsg(arguments)); - } else { - throw new Error("Not connected"); - } -}; - -TcpTransport.prototype.broadcast = function() { - var data = getmsg(arguments); - if (this._server) { - Object.keys(this._clients).forEach(function(id) { - this._clients[id].write(data); - }, this); - } else { - throw new Error("Not listening"); - } -}; - -TcpTransport.prototype.close = function() { - if (this._client) { - this._client.end(); - } else if (this._server) { - Object.keys(this._clients).forEach(function(id) { - this._clients[id].end(); - }, this); - this._server.close(); - } -}; - -module.exports = TcpTransport; diff --git a/lib/net/ws.browser.js b/lib/net/ws.browser.js deleted file mode 100644 index 5649995..0000000 --- a/lib/net/ws.browser.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * WebSocket transport. Used in browser in client-mode only. Server - * handle incoming messages and wrap them into TCP data packets. - */ - -"use strict"; - -var objectAssign = Object.assign || require("object-assign"); -var inherits = require("inherits"); -var assert = require("../_util").assert; -var structs = require("../structs"); -var messages = require("../messages"); -var BaseTransport = require("./base"); - -var ServicesBitfield = structs.ServicesBitfield; -var getmsg = BaseTransport._getmsg; - -function WsTransport(opts) { - WsTransport.super_.call(this); - objectAssign(this, opts); - this.seeds = this.seeds || []; - this.services = this.services || ServicesBitfield().set([ - ServicesBitfield.NODE_MOBILE, - ]); - this.streams = this.streams || [1]; -} - -inherits(WsTransport, BaseTransport); - -WsTransport.prototype.bootstrap = function() { - return Promise.resolve([].concat(this.seeds)); -}; - -WsTransport.prototype.connect = function(url, protocols) { - var self = this; - assert(!self._client, "Already connected"); - - // TODO(Kagami): Handle timeouts! - var client = self._client = new WebSocket(url, protocols); - client.binaryType = "arraybuffer"; - var verackSent = false; - var verackReceived = false; - var established = false; - - client.onopen = function() { - self.emit("open"); - self.send(messages.version.encode({ - services: self.services, - userAgent: self.userAgent, - streams: self.streams, - // This parameters aren't used by the remote node so we fake them - // (because we can't resolve domain name in a Browser). - remoteHost: "127.0.0.1", - remotePort: 8444, - })); - }; - - client.onmessage = function(e) { - var buf = new Buffer(new Uint8Array(e.data)); - var decoded; - try { - decoded = structs.message.decode(buf); - } catch (err) { - return self.emit("warning", new Error( - "Message decoding error: " + err.message - )); - } - self.emit("message", decoded.command, decoded.payload, decoded); - }; - - // High-level message processing. - self.on("message", function(command, payload) { - var version; - if (!established) { - if (command === "version") { - if (verackSent) { - return; - } - try { - version = self._decodeVersion(payload, {gateway: true}); - } catch(err) { - self.emit("error", err); - return client.close(); - } - self.send("verack"); - verackSent = true; - if (verackReceived) { - established = true; - self.emit("established", version); - } - } else if (command === "verack") { - verackReceived = true; - if (verackSent) { - established = true; - self.emit("established", version); - } - } - } - }); - - client.onerror = function(err) { - self.emit("error", err); - }; - - client.onclose = function() { - self.emit("close"); - delete self._client; - }; -}; - -WsTransport.prototype.send = function() { - if (this._client) { - this._client.send(getmsg(arguments)); - } else { - throw new Error("Not connected"); - } -}; - -WsTransport.prototype.close = function() { - if (this._client) { - this._client.close(); - } -}; - -module.exports = WsTransport; diff --git a/lib/net/ws.js b/lib/net/ws.js deleted file mode 100644 index 91433e3..0000000 --- a/lib/net/ws.js +++ /dev/null @@ -1,247 +0,0 @@ -/** - * WebSocket transport. Needed because browsers can't handle TCP sockets - * so we use separate WebSocket server to proxy messages into TCP data - * packets. Available for both Node.js and Browser platforms. - * **NOTE**: `WsTransport` is exported as a module. - * @example var WsTransport = require("bitmessage/lib/net/ws"); - * @module bitmessage/net/ws - */ - -"use strict"; - -var objectAssign = Object.assign || require("object-assign"); -var inherits = require("inherits"); -var WebSocket = require("ws"); // jshint ignore:line -var assert = require("../_util").assert; -var PPromise = require("../platform").Promise; -var structs = require("../structs"); -var messages = require("../messages"); -var BaseTransport = require("./base"); - -var WebSocketServer = WebSocket.Server; -var ServicesBitfield = structs.ServicesBitfield; -var getmsg = BaseTransport._getmsg; -var unmap = BaseTransport._unmap; - -/** - * WebSocket transport class. Implements - * [base transport interface]{@link - * module:bitmessage/net/base.BaseTransport}. - * @param {Object=} opts - Transport options - * @param {Array} opts.seeds - Bootstrap nodes (none by default) - * @param {Object} opts.services - - * [Service features]{@link module:bitmessage/structs.ServicesBitfield} - * provided by this node (`NODE_MOBILE` for Browser and `NODE_MOBILE` + - * `NODE_GATEWAY` for Node by default) - * @param {(Array|string|Buffer)} opts.userAgent - - * [User agent]{@link module:bitmessage/user-agent} of this node - * (user agent of bitmessage library by default) - * @param {number[]} opts.streams - Streams accepted by this node ([1] - * by default) - * @param {number} opts.port - Incoming port of this node, makes sence - * only on Node platform (18444 by default) - * @constructor - * @static - */ -function WsTransport(opts) { - WsTransport.super_.call(this); - objectAssign(this, opts); - this.seeds = this.seeds || []; - this.services = this.services || ServicesBitfield().set([ - ServicesBitfield.NODE_MOBILE, - ServicesBitfield.NODE_GATEWAY, - ]); - this.streams = this.streams || [1]; - this.port = this.port || 18444; -} - -inherits(WsTransport, BaseTransport); - -WsTransport.prototype._sendVersion = function() { - return this.send(messages.version.encode({ - services: this.services, - userAgent: this.userAgent, - streams: this.streams, - port: this.port, - remoteHost: this._client._socket.remoteAddress, - remotePort: this._client._socket.remotePort, - })); -}; - -WsTransport.prototype._handleTimeout = function() { - var client = this._client; - // TODO(Kagami): We may also want to close connection if it wasn't - // established within minute. - client._socket.setTimeout(20000); - client._socket.on("timeout", function() { - client.close(); - }); - this.on("established", function() { - // Raise timeout up to 10 minutes per spec. - // TODO(Kagami): Send ping frame every 5 minutes as PyBitmessage. - client._socket.setTimeout(600000); - }); -}; - -WsTransport.prototype._setupClient = function(client, incoming) { - var self = this; - self._client = client; - var verackSent = false; - var verackReceived = false; - var established = false; - - client.on("open", function() { - // NOTE(Kagami): This handler shouldn't be called at all for - // incoming connections but let's be sure. - if (!incoming) { - // NOTE(Kagami): We may set timeout only after connection was - // opened because socket may not yet be available when - // `_setupClient` is called. - self._handleTimeout(); - self.emit("open"); - self._sendVersion(); - } - }); - - client.on("message", function(data, flags) { - var decoded; - if (!flags.binary) { - // TODO(Kagami): Send `error` message and ban node for some time - // if there were too many errors? - return self.emit("warning", new Error("Peer sent non-binary data")); - } - try { - decoded = structs.message.decode(data); - } catch (err) { - return self.emit("warning", new Error( - "Message decoding error: " + err.message - )); - } - self.emit("message", decoded.command, decoded.payload); - }); - - // High-level message processing. - self.on("message", function(command, payload) { - var version; - var veropts = incoming ? {mobile: true} : {gateway: true}; - if (!established) { - if (command === "version") { - if (verackSent) { - return; - } - try { - version = self._decodeVersion(payload, veropts); - } catch(err) { - self.emit("error", err); - return client.close(); - } - self.send("verack"); - verackSent = true; - if (incoming) { - self._sendVersion(); - } else if (verackReceived) { - established = true; - self.emit("established", version); - } - } else if (command === "verack") { - verackReceived = true; - if (verackSent) { - established = true; - self.emit("established", version); - } - } - } - }); - - client.on("error", function(err) { - self.emit("error", err); - }); - - client.on("close", function() { - self.emit("close"); - delete self._client; - }); -}; - -WsTransport.prototype.bootstrap = function() { - return PPromise.resolve([].concat(this.seeds)); -}; - -/** - * Connect to a WebSocket node. Connection arguments are the same as for - * [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). - */ -WsTransport.prototype.connect = function(address, protocols, options) { - assert(!this._client, "Already connected"); - assert(!this._server, "Already listening"); - // `new` doesn't work with `apply`, so passing all possible arguments - // manually. - this._setupClient(new WebSocket(address, protocols, options)); -}; - -/** - * Listen for incoming WebSocket connections. Listen arguments are the - * same as for - * [WebSocketServer](https://github.com/websockets/ws#server-example). - * Available only for Node platform. - */ -WsTransport.prototype.listen = function(options, callback) { - assert(!this._client, "Already connected"); - assert(!this._server, "Already listening"); - - var self = this; - var server = self._server = new WebSocketServer(options, callback); - - server.on("connection", function(client) { - var opts = objectAssign({}, self); - delete opts._server; - var transport = new self.constructor(opts); - var incoming = true; - transport._setupClient(client, incoming); - transport._handleTimeout(); - var addr = client._socket.remoteAddress; - var port = client._socket.remotePort; - self.emit("connection", transport, unmap(addr), port); - }); - - server.on("error", function(err) { - self.emit("error", err); - }); - - // `ws` doesn't emit "close" event by default. - server._server.on("close", function() { - self.emit("close"); - delete self._server; - }); -}; - -WsTransport.prototype.send = function() { - if (this._client) { - // TODO(Kagami): `mask: true` doesn't work with Chromium 40. File a - // bug to ws bugtracker. - this._client.send(getmsg(arguments), {binary: true}); - } else { - throw new Error("Not connected"); - } -}; - -WsTransport.prototype.broadcast = function() { - var data = getmsg(arguments); - if (this._server) { - this._server.clients.forEach(function(client) { - client.send(data, {binary: true}); - }); - } else { - throw new Error("Not listening"); - } -}; - -WsTransport.prototype.close = function() { - if (this._client) { - this._client.close(); - } else if (this._server) { - this._server.close(); - } -}; - -module.exports = WsTransport; diff --git a/package.json b/package.json index 602feb1..61c65a7 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,13 @@ "description": "JavaScript Bitmessage library", "main": "./lib/index.js", "browser": { - "./lib/platform.js": "./lib/platform.browser.js", - "./lib/net/tcp.js": false, - "./lib/net/ws.js": "./lib/net/ws.browser.js" + "./lib/platform.js": "./lib/platform.browser.js" }, "scripts": { "install": "node-gyp rebuild || exit 0", - "test": "ALL_TESTS=1 mocha tests/index.js && ALL_TESTS=1 xvfb-run -a karma start --browsers Chromium && ALL_TESTS=1 xvfb-run -a karma start --browsers Firefox && jshint .", - "m": "mocha tests/index.js", - "mu": "TEST_MODE=unit mocha tests/index.js", - "mf": "TEST_MODE=functional mocha tests/index.js", + "test": "ALL_TESTS=1 mocha && ALL_TESTS=1 xvfb-run -a karma start --browsers Chromium && ALL_TESTS=1 xvfb-run -a karma start --browsers Firefox && jshint .", + "m": "mocha", "kc": "xvfb-run -a karma start --browsers Chromium", - "kcu": "TEST_MODE=unit xvfb-run -a karma start --browsers Chromium", - "kcf": "TEST_MODE=functional xvfb-run -a karma start --browsers Chromium", "kf": "xvfb-run -a karma start --browsers Firefox", "j": "jshint .", "d": "jsdoc -c jsdoc.json", @@ -59,12 +53,10 @@ "eccrypto": "^0.9.7", "es6-promise": "^2.0.1", "hash.js": "^1.0.2", - "inherits": "^2.0.1", "nan": "^1.4.1", "object-assign": "^2.0.0", "sha.js": "^2.3.1", - "webworkify": "^1.0.1", - "ws": "^0.7.1" + "webworkify": "^1.0.1" }, "optionalDependencies": { "bignum": "^0.9.0" diff --git a/tests/unit.js b/test.js similarity index 99% rename from tests/unit.js rename to test.js index 1c3781e..09fa690 100644 --- a/tests/unit.js +++ b/test.js @@ -2,8 +2,8 @@ var expect = require("chai").expect; var allTests = (global.__env__ || process.env).ALL_TESTS === "1"; var bufferEqual = require("buffer-equal"); -var bitmessage = require("../lib"); -var bmcrypto = require("../lib/crypto"); +var bitmessage = require("./lib"); +var bmcrypto = require("./lib/crypto"); var structs = bitmessage.structs; var message = structs.message; var object = structs.object; diff --git a/tests/functional.js b/tests/functional.js deleted file mode 100644 index 9c44dad..0000000 --- a/tests/functional.js +++ /dev/null @@ -1,157 +0,0 @@ -var expect = require("chai").expect; - -var bitmessage = require("../lib"); -var structs = bitmessage.structs; -var ServicesBitfield = structs.ServicesBitfield; -var message = structs.message; -var WsTransport = require("../lib/net/ws"); - -var TcpTransport, tcp; - -if (!process.browser) { - TcpTransport = require("../lib/net/tcp"); - - describe("TCP transport", function() { - before(function(done) { - tcp = new TcpTransport(); - tcp.on("error", function(err) { - console.log("TCP transport error: " + err); - }); - tcp.on("warning", function(warn) { - console.log("TCP transport warning: " + warn); - }); - // Wait some time for the server. - setTimeout(done, 300); - }); - - it("should return nothing on bootstrap by default", function() { - return tcp.bootstrap().then(function(nodes) { - expect(nodes).to.be.empty; - }); - }); - - it("should resolve DNS seeds on bootstrap", function() { - this.timeout(10000); - var tcp2 = new TcpTransport({ - dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]], - }); - return tcp2.bootstrap().then(function(nodes) { - expect(nodes).to.be.not.empty; - expect(nodes[0][1]).to.be.equal(8444); - }); - }); - - it("should return hardcoded seeds on bootstrap", function() { - this.timeout(10000); - var tcp3 = new TcpTransport({ - seeds: [["1.1.1.1", 8080]], - dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]], - }); - return tcp3.bootstrap().then(function(nodes) { - expect(nodes).to.have.length.at.least(2); - expect(nodes[0][1]).to.be.equal(8444); - expect(nodes[nodes.length - 1][0]).to.equal("1.1.1.1"); - expect(nodes[nodes.length - 1][1]).to.equal(8080); - }); - }); - - it("should allow to interconnect two nodes", function(done) { - tcp.connect(22333, "127.0.0.1"); - tcp.once("open", function() { - done(); - }); - }); - - it("should automatically establish connection", function(done) { - tcp.once("established", function(version) { - expect(version.protoVersion).to.equal(3); - expect(version.services.get(ServicesBitfield.NODE_NETWORK)).to.be.true; - expect(version.remoteHost).to.equal("127.0.0.1"); - expect(version.port).to.equal(22333); - expect(version.userAgent).to.be.a("string"); - expect(version.streams).to.deep.equal([1]); - done(); - }); - }); - - it("should allow to communicate", function(done) { - tcp.on("message", function cb(command, payload) { - if (command === "echo-res") { - expect(payload.toString()).to.equal("test"); - tcp.removeListener("message", cb); - done(); - } - }); - tcp.send("echo-req", Buffer("test")); - }); - - it("should allow to close connection", function(done) { - tcp.close(); - tcp.once("close", function() { - done(); - }); - }); - }); -} - -describe("WebSocket transport", function() { - var ws; - - before(function(done) { - ws = new WsTransport(); - ws.on("error", function(err) { - console.log("WebSocket transport error: " + err); - }); - ws.on("warning", function(warn) { - console.log("WebSocket transport warning: " + warn); - }); - // Wait some time for the server. - setTimeout(done, 300); - }); - - it("should return hardcoded seeds on bootstrap", function() { - var ws2 = new WsTransport({seeds: [["ws.example.com", 8080]]}); - return ws2.bootstrap().then(function(nodes) { - expect(nodes).to.have.length(1); - expect(nodes[0][0]).to.be.equal("ws.example.com"); - expect(nodes[0][1]).to.be.equal(8080); - }); - }); - - it("should allow to interconnect two nodes", function(done) { - ws.connect("ws://127.0.0.1:22334"); - ws.once("open", function() { - done(); - }); - }); - - it("should automatically establish connection", function(done) { - ws.once("established", function(version) { - expect(version.protoVersion).to.equal(3); - expect(version.services.get(ServicesBitfield.NODE_GATEWAY)).to.be.true; - expect(version.remoteHost).to.equal("127.0.0.1"); - expect(version.port).to.equal(22334); - expect(version.userAgent).to.be.a("string"); - expect(version.streams).to.deep.equal([1]); - done(); - }); - }); - - it("should allow to communicate", function(done) { - ws.on("message", function cb(command, payload) { - if (command === "echo-res") { - expect(payload.toString()).to.equal("test"); - ws.removeListener("message", cb); - done(); - } - }); - ws.send("echo-req", Buffer("test")); - }); - - it("should allow to close connection", function(done) { - ws.close(); - ws.once("close", function() { - done(); - }); - }); -}); diff --git a/tests/index.js b/tests/index.js deleted file mode 100644 index dc06590..0000000 --- a/tests/index.js +++ /dev/null @@ -1,17 +0,0 @@ -var testMode = (global.__env__ || process.env).TEST_MODE; - -if (testMode !== "functional") { - describe("Unit tests", function() { - require("./unit"); - }); -} - -if (testMode !== "unit") { - // For Browser tests nodes are being run from karma.conf.js because we - // are _already_ in browser context here. - if (!process.browser) require("./run-test-nodes.js")(); - - describe("Functional tests", function() { - require("./functional"); - }); -} diff --git a/tests/run-test-nodes.js b/tests/run-test-nodes.js deleted file mode 100644 index 9f5eded..0000000 --- a/tests/run-test-nodes.js +++ /dev/null @@ -1,43 +0,0 @@ -// Run test nodes for all known transports so we can test library in -// functional test suites. Stop nodes when testing is complete. - -// Note that this file is executed only on Node.js platform. - -var path = require("path"); -var child = require("child_process"); - -function spawn(name) { - var p = child.spawn("node", [path.join(__dirname, name)]); - p.stdout.on("data", function(data) { - console.log("Info from " + name + ": " + data.toString().trim()); - }); - p.stderr.on("data", function(err) { - console.log("Error from " + name + ": " + err.toString().trim()); - }); - return p; -} - -module.exports = function() { - function cleanup(doExit) { - return function(err) { - try { - tcpNode.kill("SIGKILL"); - } catch(e) { - console.log(e.stack); - } - try { - wsNode.kill("SIGKILL"); - } catch(e) { - console.log(e.stack); - } - if (err && err.stack) console.log(err.stack); - if (doExit) process.exit(1); - }; - } - - process.on("exit", cleanup()); - process.on("SIGINT", cleanup(true)); - - var tcpNode = spawn("tcp-node.js"); - var wsNode = spawn("ws-node.js"); -}; diff --git a/tests/tcp-node.js b/tests/tcp-node.js deleted file mode 100644 index c148303..0000000 --- a/tests/tcp-node.js +++ /dev/null @@ -1,17 +0,0 @@ -var TcpTransport = require("../lib/net/tcp"); - -function start() { - var server = new TcpTransport({port: 22333}); - // In node 0.12/io 1.0 we can use {host: x, port: y} syntax so it'll - // be more compatible with the ws transport options. - server.listen(22333, "127.0.0.1"); - server.on("connection", function(client) { - client.on("message", function(command, payload) { - if (command === "echo-req") { - client.send("echo-res", payload); - } - }); - }); -} - -start(); diff --git a/tests/ws-node.js b/tests/ws-node.js deleted file mode 100644 index 8c7ecc9..0000000 --- a/tests/ws-node.js +++ /dev/null @@ -1,15 +0,0 @@ -var WsTransport = require("../lib/net/ws"); - -function start() { - var server = new WsTransport({port: 22334}); - server.listen({host: "127.0.0.1", port: 22334}); - server.on("connection", function(client) { - client.on("message", function(command, payload) { - if (command === "echo-req") { - client.send("echo-res", payload); - } - }); - }); -} - -start();