Extract transports to bitmessage-transports

Fixes #13
This commit is contained in:
Kagami Hiiragi 2015-02-25 13:28:58 +03:00
parent dd8648e582
commit 84ad9eb2c2
13 changed files with 12 additions and 1153 deletions

View File

@ -52,10 +52,6 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
- [x] High-level classes - [x] High-level classes
- [x] Address - [x] Address
- [x] UserAgent - [x] UserAgent
- [ ] Network transports
- [x] TCP (Node.js only)
- [x] WebSocket
- [ ] WebRTC
- [ ] PyBitmessage configs parsing - [ ] PyBitmessage configs parsing
- [ ] keys.dat - [ ] keys.dat
- [ ] knownnodes.dat - [ ] knownnodes.dat
@ -129,9 +125,11 @@ console.log(structs.message.decode(verackmsg).command); // verack
### Networking ### Networking
You will need to install [bitmessage-transports](https://github.com/bitchan/bitmessage-transports) library.
```js ```js
var messages = require("bitmessage").messages; var messages = require("bitmessage").messages;
var TcpTransport = require("bitmessage/lib/net/tcp"); var TcpTransport = require("bitmessage-transports").TcpTransport;
var tcp = new TcpTransport({ var tcp = new TcpTransport({
dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]], dnsSeeds: [["bootstrap8444.bitmessage.org", 8444]],

View File

@ -1,6 +1,5 @@
process.env.CHROME_BIN = "chromium-browser"; process.env.CHROME_BIN = "chromium-browser";
var allTests = process.env.ALL_TESTS === "1"; var allTests = process.env.ALL_TESTS === "1";
if (process.env.TEST_MODE !== "unit") require("./tests/run-test-nodes.js")();
module.exports = function(config) { module.exports = function(config) {
config.set({ config.set({
@ -15,20 +14,17 @@ module.exports = function(config) {
// list of files / patterns to load in the browser // 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 // preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: { preprocessors: {
"tests/index.js": ["browserify", "env"], "test.js": ["browserify", "env"],
}, },
envPreprocessor: [ envPreprocessor: ["ALL_TESTS"],
"ALL_TESTS",
"TEST_MODE",
],
// list of files to exclude // list of files to exclude

View File

@ -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.<Array>}
* @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;

View File

@ -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: <https://github.com/Bitmessage/PyBitmessage/issues/768>.
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.<Array>} 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;

View File

@ -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;

View File

@ -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;

View File

@ -4,19 +4,13 @@
"description": "JavaScript Bitmessage library", "description": "JavaScript Bitmessage library",
"main": "./lib/index.js", "main": "./lib/index.js",
"browser": { "browser": {
"./lib/platform.js": "./lib/platform.browser.js", "./lib/platform.js": "./lib/platform.browser.js"
"./lib/net/tcp.js": false,
"./lib/net/ws.js": "./lib/net/ws.browser.js"
}, },
"scripts": { "scripts": {
"install": "node-gyp rebuild || exit 0", "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 .", "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 tests/index.js", "m": "mocha",
"mu": "TEST_MODE=unit mocha tests/index.js",
"mf": "TEST_MODE=functional mocha tests/index.js",
"kc": "xvfb-run -a karma start --browsers Chromium", "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", "kf": "xvfb-run -a karma start --browsers Firefox",
"j": "jshint .", "j": "jshint .",
"d": "jsdoc -c jsdoc.json", "d": "jsdoc -c jsdoc.json",
@ -59,12 +53,10 @@
"eccrypto": "^0.9.7", "eccrypto": "^0.9.7",
"es6-promise": "^2.0.1", "es6-promise": "^2.0.1",
"hash.js": "^1.0.2", "hash.js": "^1.0.2",
"inherits": "^2.0.1",
"nan": "^1.4.1", "nan": "^1.4.1",
"object-assign": "^2.0.0", "object-assign": "^2.0.0",
"sha.js": "^2.3.1", "sha.js": "^2.3.1",
"webworkify": "^1.0.1", "webworkify": "^1.0.1"
"ws": "^0.7.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"bignum": "^0.9.0" "bignum": "^0.9.0"

View File

@ -2,8 +2,8 @@ var expect = require("chai").expect;
var allTests = (global.__env__ || process.env).ALL_TESTS === "1"; var allTests = (global.__env__ || process.env).ALL_TESTS === "1";
var bufferEqual = require("buffer-equal"); var bufferEqual = require("buffer-equal");
var bitmessage = require("../lib"); var bitmessage = require("./lib");
var bmcrypto = require("../lib/crypto"); var bmcrypto = require("./lib/crypto");
var structs = bitmessage.structs; var structs = bitmessage.structs;
var message = structs.message; var message = structs.message;
var object = structs.object; var object = structs.object;

View File

@ -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();
});
});
});

View File

@ -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");
});
}

View File

@ -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");
};

View File

@ -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();

View File

@ -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();