From 4e876511a3ef3ce79d76d8ead3c7eefb4ee743db Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Fri, 26 Jul 2013 06:52:20 +0900 Subject: [PATCH 01/30] Connect to servers on an interval --- src/js/ripple/remote.js | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index 1ecb16dd..d1cb5b16 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -319,9 +319,9 @@ function Remote(opts, trace) { this.local_sequence = opts.local_sequence; // Locally track sequence numbers this.local_fee = opts.local_fee; // Locally set fees this.local_signing = (typeof opts.local_signing === 'undefined') - ? true : opts.local_signing; + ? true : Boolean(opts.local_signing); this.fee_cushion = (typeof opts.fee_cushion === 'undefined') - ? 1.5 : opts.fee_cushion; + ? 1.5 : Number(opts.fee_cushion); this.id = 0; this.trace = opts.trace || trace; @@ -352,10 +352,10 @@ function Remote(opts, trace) { // Local signing implies local fees and sequences if (this.local_signing) { this.local_sequence = true; - this.local_fee = true; + this.local_fee = true; } - this._servers = [ ]; + this._servers = [ ]; this._primary_server = void(0); // Cache information for accounts. @@ -367,13 +367,13 @@ function Remote(opts, trace) { // account : { seq : __ } }; - // Hash map of Account objects by AccountId. + // Account objects by AccountId. this._accounts = {}; - // Hash map of OrderBook objects + // OrderBook objects this._books = {}; - // List of secrets that we know about. + // Secrets that we know about. this.secrets = { // Secrets can be set by calling set_secret(account, secret). @@ -383,8 +383,8 @@ function Remote(opts, trace) { // Cache for various ledgers. // XXX Clear when ledger advances. this.ledgers = { - 'current' : { - 'account_root' : {} + current : { + account_root : {} } }; @@ -491,11 +491,14 @@ Remote.prototype.add_server = function (opts) { }); server.on('connect', function () { + self._connection_count++; + self._set_state('online'); if (opts.primary || !self._primary_server) { self._set_primary_server(server); } - self._connection_count++; - self._set_state('online'); + if (self._connection_count === self._servers.length) { + self.emit('ready'); + } }); server.on('disconnect', function () { @@ -555,9 +558,11 @@ Remote.prototype.connect = function (online) { switch(typeof online) { case 'undefined': break; + case 'function': this.once('connect', online); break; + default: if (!Boolean(online)) return this.disconnect() @@ -567,9 +572,15 @@ Remote.prototype.connect = function (online) { if (!this._servers.length) { throw new Error('No servers available.'); } else { - for (var i=0; i Date: Fri, 26 Jul 2013 07:25:10 +0900 Subject: [PATCH 02/30] Safer response check, add _remote_address(), attach server to request --- src/js/ripple/server.js | 76 +++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index a9275f5d..32f8a50b 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -5,13 +5,13 @@ var utils = require('./utils'); /** * @constructor Server * @param remote The Remote object - * @param cfg Configuration parameters. + * @param opts Configuration parameters. * * Keys for cfg: * url */ -var Server = function (remote, opts) { +function Server(remote, opts) { EventEmitter.call(this); if (typeof opts !== 'object' || typeof opts.url !== 'string') { @@ -60,7 +60,7 @@ Server.online_states = [ Server.prototype._is_online = function (status) { return Server.online_states.indexOf(status) !== -1; -}; +} Server.prototype._set_state = function (state) { if (state !== this._state) { @@ -76,7 +76,15 @@ Server.prototype._set_state = function (state) { this.emit('disconnect'); } } -}; +} + +Server.prototype._remote_address = function() { + var address = null; + if (this._ws) { + address = this._ws._socket.remoteAddress; + } + return address; +} Server.prototype.connect = function () { var self = this; @@ -174,7 +182,7 @@ Server.prototype.connect = function () { ws.onmessage = function (msg) { self.emit('message', msg.data); }; -}; +} Server.prototype.disconnect = function () { this._should_connect = false; @@ -182,11 +190,13 @@ Server.prototype.disconnect = function () { if (this._ws) { this._ws.close(); } -}; +} Server.prototype.send_message = function (message) { - this._ws.send(JSON.stringify(message)); -}; + if (this._ws) { + this._ws.send(JSON.stringify(message)); + } +} /** * Submit a Request object to this server. @@ -195,31 +205,32 @@ Server.prototype.request = function (request) { var self = this; // Only bother if we are still connected. - if (self._ws) { - request.message.id = self._id; + if (this._ws) { + request.server = this; + request.message.id = this._id; - self._requests[request.message.id] = request; + this._requests[request.message.id] = request; // Advance message ID - self._id++; + this._id++; - if (self._connected || (request.message.command === 'subscribe' && self._ws.readyState === 1)) { - if (self._remote.trace) { + if (this._connected || (request.message.command === 'subscribe' && this._ws.readyState === 1)) { + if (this._remote.trace) { utils.logObject('server: request: %s', request.message); } - self.send_message(request.message); + this.send_message(request.message); } else { - // XXX There are many ways to make self smarter. - self.once('connect', function () { - if (self._remote.trace) { + // XXX There are many ways to make this smarter. + this.once('connect', function () { + if (this._remote.trace) { utils.logObject('server: request: %s', request.message); } self.send_message(request.message); }); } } else { - if (self._remote.trace) { + if (this._remote.trace) { utils.logObject('server: request: DROPPING: %s', request.message); } } @@ -227,13 +238,15 @@ Server.prototype.request = function (request) { Server.prototype._handle_message = function (json) { var self = this; - - var message; try { - message = JSON.parse(json); + var message = JSON.parse(json); } catch(exception) { return; } + if (typeof message !== 'object' || typeof message.type === 'undefined') { + return; + } + switch(message.type) { case 'response': // A response to a request. @@ -255,9 +268,9 @@ Server.prototype._handle_message = function (json) { if (self._remote.trace) utils.logObject('server: error: %s', message); request.emit('error', { - 'error' : 'remoteError', - 'error_message' : 'Remote reported an error.', - 'remote' : message + error : 'remoteError', + error_message : 'Remote reported an error.', + remote : message }); } break; @@ -267,17 +280,14 @@ Server.prototype._handle_message = function (json) { self._set_state(self._is_online(message.server_status) ? 'online' : 'offline'); break; } -}; +} Server.prototype._handle_response_subscribe = function (message) { - var self = this; - - self._server_status = message.server_status; - - if (self._is_online(message.server_status)) { - self._set_state('online'); + this._server_status = message.server_status; + if (this._is_online(message.server_status)) { + this._set_state('online'); } -}; +} exports.Server = Server; From 342d5a1aa163efd179278912675df915c00d80c5 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Sat, 27 Jul 2013 04:03:33 +0900 Subject: [PATCH 03/30] Update remote --- src/js/ripple/remote.js | 465 ++++++++++------------------------------ 1 file changed, 113 insertions(+), 352 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index d1cb5b16..97c40e03 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -15,266 +15,24 @@ // // npm -var EventEmitter = require('events').EventEmitter; -var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var util = require('util'); -var Server = require('./server').Server; -var Amount = require('./amount').Amount; -var Currency = require('./currency').Currency; -var UInt160 = require('./uint160').UInt160; -var Transaction = require('./transaction').Transaction; -var Account = require('./account').Account; -var Meta = require('./meta').Meta; -var OrderBook = require('./orderbook').OrderBook; +var Request = require('./request').Request; +var Server = require('./server').Server; +var Amount = require('./amount').Amount; +var Currency = require('./currency').Currency; +var UInt160 = require('./uint160').UInt160; +var Transaction = require('./transaction').Transaction; +var Account = require('./account').Account; +var Meta = require('./meta').Meta; +var OrderBook = require('./orderbook').OrderBook; -var utils = require('./utils'); -var config = require('./config'); -var sjcl = require('../../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var sjcl = require('../../../build/sjcl'); -// Request events emitted: -// 'success' : Request successful. -// 'error' : Request failed. -// 'remoteError' -// 'remoteUnexpected' -// 'remoteDisconnected' -function Request(remote, command) { - EventEmitter.call(this); - this.remote = remote; - this.requested = false; - this.message = { - command : command, - id : void(0) - }; -}; -util.inherits(Request, EventEmitter); - -// Send the request to a remote. -Request.prototype.request = function (remote) { - if (!this.requested) { - this.requested = true; - this.remote.request(this); - this.emit('request', remote); - } -}; - -Request.prototype.callback = function(callback, successEvent, errorEvent) { - if (callback && typeof callback === 'function') { - this.once(successEvent || 'success', callback.bind(this, null)); - this.once(errorEvent || 'error' , callback.bind(this)); - this.request(); - } - - return this; -}; - -Request.prototype.timeout = function(duration, callback) { - if (!this.requested) { - this.once('request', this.timeout.bind(this, duration, callback)); - return; - }; - - var self = this; - var emit = this.emit; - var timed_out = false; - - var timeout = setTimeout(function() { - timed_out = true; - if (typeof callback === 'function') callback(); - emit.call(self, 'timeout'); - }, duration); - - this.emit = function() { - if (timed_out) return; - else clearTimeout(timeout); - emit.apply(self, arguments); - }; - - return this; -}; - -Request.prototype.build_path = function (build) { - if (build) { - this.message.build_path = true; - } - - return this; -}; - -Request.prototype.ledger_choose = function (current) { - if (current) { - this.message.ledger_index = this.remote._ledger_current_index; - } else { - this.message.ledger_hash = this.remote._ledger_hash; - } - - return this; -}; - -// Set the ledger for a request. -// - ledger_entry -// - transaction_entry -Request.prototype.ledger_hash = function (h) { - this.message.ledger_hash = h; - - return this; -}; - -// Set the ledger_index for a request. -// - ledger_entry -Request.prototype.ledger_index = function (ledger_index) { - this.message.ledger_index = ledger_index; - - return this; -}; - -Request.prototype.ledger_select = function (ledger_spec) { - switch (ledger_spec) { - case 'current': - case 'closed': - case 'verified': - this.message.ledger_index = ledger_spec; - break; - - default: - // XXX Better test needed - if (String(ledger_spec).length > 12) { - this.message.ledger_hash = ledger_spec; - } else { - this.message.ledger_index = ledger_spec; - } - break; - } - - return this; -}; - -Request.prototype.account_root = function (account) { - this.message.account_root = UInt160.json_rewrite(account); - - return this; -}; - -Request.prototype.index = function (hash) { - this.message.index = hash; - - return this; -}; - -// Provide the information id an offer. -// --> account -// --> seq : sequence number of transaction creating offer (integer) -Request.prototype.offer_id = function (account, seq) { - this.message.offer = { - account: UInt160.json_rewrite(account), - seq: seq - }; - - return this; -}; - -// --> index : ledger entry index. -Request.prototype.offer_index = function (index) { - this.message.offer = index; - - return this; -}; - -Request.prototype.secret = function (s) { - if (s) { - this.message.secret = s; - } - - return this; -}; - -Request.prototype.tx_hash = function (h) { - this.message.tx_hash = h; - - return this; -}; - -Request.prototype.tx_json = function (j) { - this.message.tx_json = j; - - return this; -}; - -Request.prototype.tx_blob = function (j) { - this.message.tx_blob = j; - - return this; -}; - -Request.prototype.ripple_state = function (account, issuer, currency) { - this.message.ripple_state = { - 'accounts' : [ - UInt160.json_rewrite(account), - UInt160.json_rewrite(issuer) - ], - 'currency' : currency - }; - - return this; -}; - -Request.prototype.accounts = function (accounts, realtime) { - if (!Array.isArray(accounts)) { - accounts = [ accounts ]; - } - - // Process accounts parameters - var procAccounts = accounts.map(function(account) { - return UInt160.json_rewrite(account); - }); - - if (realtime) { - this.message.rt_accounts = procAccounts; - } else { - this.message.accounts = procAccounts; - } - - return this; -}; - -Request.prototype.rt_accounts = function (accounts) { - return this.accounts(accounts, true); -}; - -Request.prototype.books = function (books, snapshot) { - var procBooks = []; - - for (var i = 0, l = books.length; i < l; i++) { - var book = books[i]; - var json = {}; - - function processSide(side) { - if (!book[side]) throw new Error('Missing '+side); - - var obj = json[side] = { - currency: Currency.json_rewrite(book[side].currency) - }; - - if (obj.currency !== 'XRP') { - obj.issuer = UInt160.json_rewrite(book[side].issuer); - } - } - - processSide('taker_gets'); - processSide('taker_pays'); - - if (snapshot) json.snapshot = true; - if (book.both) json.both = true; - - procBooks.push(json); - } - - this.message.books = procBooks; - - return this; -}; - -//------------------------------------------------------------------------------ /** Interface to manage the connection to a Ripple server. @@ -606,93 +364,95 @@ Remote.prototype.ledger_hash = function () { // It is possible for messages to be dispatched after the connection is closed. Remote.prototype._handle_message = function (json) { var self = this; - var message = JSON.parse(json); var unexpected = false; - var request; + var message - if (typeof message !== 'object') { - unexpected = true; - } else { - switch (message.type) { - case 'response': - // Handled by the server that sent the request - break; - - case 'ledgerClosed': - // XXX If not trusted, need to verify we consider ledger closed. - // XXX Also need to consider a slow server or out of order response. - // XXX Be more defensive fields could be missing or of wrong type. - // YYY Might want to do some cache management. - - this._ledger_time = message.ledger_time; - this._ledger_hash = message.ledger_hash; - this._ledger_current_index = message.ledger_index + 1; - - this.emit('ledger_closed', message); - break; - - case 'transaction': - // To get these events, just subscribe to them. A subscribes and - // unsubscribes will be added as needed. - // XXX If not trusted, need proof. - - // De-duplicate transactions that are immediately following each other - // XXX Should have a cache of n txs so we can dedup out of order txs - if (this._last_tx === message.transaction.hash) break; - this._last_tx = message.transaction.hash; - - if (this.trace) utils.logObject('remote: tx: %s', message); - - // Process metadata - message.mmeta = new Meta(message.meta); - - // Pass the event on to any related Account objects - var affected = message.mmeta.getAffectedAccounts(); - for (var i = 0, l = affected.length; i < l; i++) { - var account = self._accounts[affected[i]]; - - if (account) account.notifyTx(message); - } - - // Pass the event on to any related OrderBooks - affected = message.mmeta.getAffectedBooks(); - for (i = 0, l = affected.length; i < l; i++) { - var book = self._books[affected[i]]; - - if (book) book.notifyTx(message); - } - - this.emit('transaction', message); - this.emit('transaction_all', message); - break; - - // XXX Should be tracked by the Server object - case 'serverStatus': - if ('load_base' in message && 'load_factor' in message && - (message.load_base !== self._load_base || message.load_factor != self._load_factor)) - { - self._load_base = message.load_base; - self._load_factor = message.load_factor; - - self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); - } - break; - - // All other messages - default: - if (this.trace) utils.logObject('remote: '+message.type+': %s', message); - this.emit('net_' + message.type, message); - break; - } + try { + message = JSON.parse(json); + unexpected = typeof message !== 'object'; + } catch(exception) { + unexpected = true; } - // Unexpected response from remote. if (unexpected) { - console.log('unexpected message from trusted remote: %s', json); - (request || this).emit('error', { - 'error' : 'remoteUnexpected', - 'error_message' : 'Unexpected response from remote.' + // Unexpected response from remote. + this.emit('error', { + error: 'remoteUnexpected', + error_message: 'Unexpected response from remote.' }); + return; + } + + switch (message.type) { + case 'response': + // Handled by the server that sent the request + break; + + case 'ledgerClosed': + // XXX If not trusted, need to verify we consider ledger closed. + // XXX Also need to consider a slow server or out of order response. + // XXX Be more defensive fields could be missing or of wrong type. + // YYY Might want to do some cache management. + + this._ledger_time = message.ledger_time; + this._ledger_hash = message.ledger_hash; + this._ledger_current_index = message.ledger_index + 1; + + this.emit('ledger_closed', message); + break; + + case 'transaction': + // To get these events, just subscribe to them. A subscribes and + // unsubscribes will be added as needed. + // XXX If not trusted, need proof. + + // De-duplicate transactions that are immediately following each other + // XXX Should have a cache of n txs so we can dedup out of order txs + if (this._last_tx === message.transaction.hash) break; + this._last_tx = message.transaction.hash; + + if (this.trace) utils.logObject('remote: tx: %s', message); + + // Process metadata + message.mmeta = new Meta(message.meta); + + // Pass the event on to any related Account objects + var affected = message.mmeta.getAffectedAccounts(); + for (var i=0, l=affected.length; i Date: Sat, 27 Jul 2013 05:57:30 +0900 Subject: [PATCH 04/30] Accommodate new transaction manager --- src/js/ripple/account.js | 73 +++--- src/js/ripple/transaction.js | 438 +++++++++++------------------------ 2 files changed, 176 insertions(+), 335 deletions(-) diff --git a/src/js/ripple/account.js b/src/js/ripple/account.js index 603ad7d1..f92296f4 100644 --- a/src/js/ripple/account.js +++ b/src/js/ripple/account.js @@ -11,43 +11,45 @@ // var network = require("./network.js"); -var EventEmitter = require('events').EventEmitter; -var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var util = require('util'); +var extend = require('extend'); -var Amount = require('./amount').Amount; -var UInt160 = require('./uint160').UInt160; +var Amount = require('./amount').Amount; +var UInt160 = require('./uint160').UInt160; +var TransactionManager = require('./transactionmanager').TransactionManager; -var extend = require('extend'); -var Account = function (remote, account) { +function Account(remote, account) { EventEmitter.call(this); + var self = this; - this._remote = remote; - this._account = UInt160.from_json(account); + this._tx_manager = null; + this._remote = remote; + this._account = UInt160.from_json(account); this._account_id = this._account.to_json(); - this._subs = 0; + this._subs = 0; // Ledger entry object // Important: This must never be overwritten, only extend()-ed - this._entry = {}; + this._entry = { }; this.on('newListener', function (type, listener) { - if (Account.subscribe_events.indexOf(type) !== -1) { - if (!self._subs && 'open' === self._remote._online_state) { + if (~Account.subscribe_events.indexOf(type)) { + if (!self._subs && self._remote._connected) { self._remote.request_subscribe() .accounts(self._account_id) .request(); } - self._subs += 1; + self._subs += 1; } }); this.on('removeListener', function (type, listener) { - if (Account.subscribe_events.indexOf(type) !== -1) { - self._subs -= 1; - - if (!self._subs && 'open' === self._remote._online_state) { + if (~Account.subscribe_events.indexOf(type)) { + self._subs -= 1; + if (!self._subs && self._remote._connected) { self._remote.request_unsubscribe() .accounts(self._account_id) .request(); @@ -83,8 +85,7 @@ util.inherits(Account, EventEmitter); */ Account.subscribe_events = ['transaction', 'entry']; -Account.prototype.to_json = function () -{ +Account.prototype.to_json = function () { return this._account.to_json(); }; @@ -93,8 +94,7 @@ Account.prototype.to_json = function () * * Note: This does not tell you whether the account exists in the ledger. */ -Account.prototype.is_valid = function () -{ +Account.prototype.is_valid = function () { return this._account.is_valid(); }; @@ -106,18 +106,17 @@ Account.prototype.is_valid = function () * * @param {function (err, entry)} callback Called with the result */ -Account.prototype.entry = function (callback) -{ +Account.prototype.entry = function (callback) { var self = this; + var callback = typeof callback === 'function' + ? callback + : function(){}; self._remote.request_account_info(this._account_id) .on('success', function (e) { extend(self._entry, e.account_data); self.emit('entry', self._entry); - - if ("function" === typeof callback) { - callback(null, e); - } + callback(null, e); }) .on('error', function (e) { callback(e); @@ -127,14 +126,23 @@ Account.prototype.entry = function (callback) return this; }; +Account.prototype.get_next_sequence = function(callback) { + this._remote.request_account_info(this._account_id, function(err, entry) { + if (err) { + callback(err); + } else { + callback(null, entry.account_data.Sequence); + } + }); +}; + /** * Notify object of a relevant transaction. * * This is only meant to be called by the Remote class. You should never have to * call this yourself. */ -Account.prototype.notifyTx = function (message) -{ +Account.prototype.notifyTx = function (message) { // Only trigger the event if the account object is actually // subscribed - this prevents some weird phantom events from // occurring. @@ -143,6 +151,13 @@ Account.prototype.notifyTx = function (message) } }; +Account.prototype.submit = function(tx) { + if (!this._tx_manager) { + this._tx_manager = new TransactionManager(this); + } + this._tx_manager.submit(tx); +}; + exports.Account = Account; // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/ripple/transaction.js b/src/js/ripple/transaction.js index 8cabe68e..43687493 100644 --- a/src/js/ripple/transaction.js +++ b/src/js/ripple/transaction.js @@ -1,4 +1,3 @@ -// // Transactions // // Construction: @@ -56,83 +55,63 @@ var SerializedObject = require('./serializedobject').SerializedObject; var config = require('./config'); -var SUBMIT_MISSING = 4; // Report missing. -var SUBMIT_LOST = 8; // Give up tracking. - // A class to implement transactions. // - Collects parameters // - Allow event listeners to be attached to determine the outcome. -var Transaction = function (remote) { +function Transaction(remote) { EventEmitter.call(this); - // YYY Make private as many variables as possible. var self = this; - this.callback = undefined; - this.remote = remote; - this._secret = undefined; - this._build_path = false; + this.remote = remote; + this._secret = void(0); + this._build_path = false; // Transaction data. - this.tx_json = { - 'Flags' : 0, // XXX Would be nice if server did not require this. - }; + this.tx_json = { Flags: 0 }; - this.hash = undefined; - this.submit_index = undefined; // ledger_current_index was this when transaction was submited. - this.state = undefined; // Under construction. - this.finalized = false; + this.hash = void(0); - this.on('success', function (message) { - if (message.engine_result) { - self.hash = message.tx_json.hash; + // ledger_current_index was this when transaction was submited. + this.submit_index = void(0); - self.set_state('client_proposed'); + // Under construction. + this.state = void(0); - self.emit('proposed', { - 'tx_json' : message.tx_json, - 'result' : message.engine_result, - 'result_code' : message.engine_result_code, - 'result_message' : message.engine_result_message, - 'rejected' : self.isRejected(message.engine_result_code), // If server is honest, don't expect a final if rejected. - }); - } - }); - - this.on('error', function (message) { - // Might want to give more detailed information. - self.set_state('remoteError'); - }); + this.finalized = false; + this._previous_signing_hash = void(0); }; util.inherits(Transaction, EventEmitter); // XXX This needs to be determined from the network. Transaction.fees = { - 'default' : 10, + default : Amount.from_json('10'), + nickname_create : Amount.from_json('1000'), + offer : Amount.from_json('10'), }; Transaction.flags = { - 'AccountSet' : { - 'RequireDestTag' : 0x00010000, - 'OptionalDestTag' : 0x00020000, - 'RequireAuth' : 0x00040000, - 'OptionalAuth' : 0x00080000, - 'DisallowXRP' : 0x00100000, - 'AllowXRP' : 0x00200000, + AccountSet : { + RequireDestTag : 0x00010000, + OptionalDestTag : 0x00020000, + RequireAuth : 0x00040000, + OptionalAuth : 0x00080000, + DisallowXRP : 0x00100000, + AllowXRP : 0x00200000, }, - 'OfferCreate' : { - 'Passive' : 0x00010000, - 'ImmediateOrCancel' : 0x00020000, - 'FillOrKill' : 0x00040000, - 'Sell' : 0x00080000, + OfferCreate : { + Passive : 0x00010000, + ImmediateOrCancel : 0x00020000, + FillOrKill : 0x00040000, + Sell : 0x00080000, }, - 'Payment' : { - 'NoRippleDirect' : 0x00010000, - 'PartialPayment' : 0x00020000, - 'LimitQuality' : 0x00040000, + Payment : { + NoRippleDirect : 0x00010000, + PartialPayment : 0x00020000, + LimitQuality : 0x00040000, }, }; @@ -185,6 +164,15 @@ Transaction.prototype.set_state = function (state) { } }; +/** + * TODO + * Actually do this right + */ + +Transaction.prototype.get_fee = function() { + return Transaction.fees['default'].to_json(); +}; + /** * Attempts to complete the transaction for submission. * @@ -195,11 +183,11 @@ Transaction.prototype.set_state = function (state) { Transaction.prototype.complete = function () { var tx_json = this.tx_json; - if ("undefined" === typeof tx_json.Fee && this.remote.local_fee) { - this.tx_json.Fee = "" + Math.ceil(this.remote.fee_tx() * this.fee_units()); + if (tx_json.Fee === void(0) && this.remote.local_fee) { + tx_json.Fee = Transaction.fees['default'].to_json(); } - if ("undefined" === typeof tx_json.SigningPubKey && (!this.remote || this.remote.local_signing)) { + if (tx_json.SigningPubKey === void(0) && (!this.remote || this.remote.local_signing)) { var seed = Seed.from_json(this._secret); var key = seed.get_key(this.tx_json.Account); tx_json.SigningPubKey = key.to_hex_pub(); @@ -223,6 +211,11 @@ Transaction.prototype.signing_hash = function () { Transaction.prototype.sign = function () { var seed = Seed.from_json(this._secret); var hash = this.signing_hash(); + + if (this.tx_json.TxnSignature && hash === this._previous_signing_hash) { + return; + } + var key = seed.get_key(this.tx_json.Account); var sig = key.sign(hash, 0); var hex = sjcl.codec.hex.fromBits(sig).toUpperCase(); @@ -236,206 +229,6 @@ Transaction.prototype._hasTransactionListeners = function() { || this.listeners('pending').length }; -// Submit a transaction to the network. -// XXX Don't allow a submit without knowing ledger_index. -// XXX Have a network canSubmit(), post events for following. -// XXX Also give broader status for tracking through network disconnects. -// callback = function (status, info) { -// // status is final status. Only works under a ledger_accepting conditions. -// switch status: -// case 'tesSUCCESS': all is well. -// case 'tejSecretUnknown': unable to sign transaction - secret unknown -// case 'tejServerUntrusted': sending secret to untrusted server. -// case 'tejInvalidAccount': locally detected error. -// case 'tejLost': locally gave up looking -// default: some other TER -// } - -Transaction.prototype.submit = function (callback) { - var self = this; - var tx_json = this.tx_json; - - this.callback = typeof callback === 'function' - ? callback - : function(){}; - - function finish(err) { - self.emit('error', err); - self.callback('error', err); - } - - if (typeof tx_json.Account !== 'string') { - finish({ - 'error' : 'tejInvalidAccount', - 'error_message' : 'Bad account.' - }); - return this; - } - - // YYY Might check paths for invalid accounts. - - this.complete(); - - //console.log('Callback or has listeners'); - - // There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them. - - this.submit_index = this.remote._ledger_current_index; - - // When a ledger closes, look for the result. - function on_ledger_closed(message) { - if (self.finalized) return; - - var ledger_hash = message.ledger_hash; - var ledger_index = message.ledger_index; - var stop = false; - - // XXX make sure self.hash is available. - var transaction_entry = self.remote.request_transaction_entry(self.hash) - - transaction_entry.ledger_hash(ledger_hash) - - transaction_entry.on('success', function (message) { - if (self.finalized) return; - self.set_state(message.metadata.TransactionResult); - self.remote.removeListener('ledger_closed', on_ledger_closed); - self.emit('final', message); - self.finalized = true; - self.callback(message.metadata.TransactionResult, message); - }); - - transaction_entry.on('error', function (message) { - if (self.finalized) return; - - if (message.error === 'remoteError' && message.remote.error === 'transactionNotFound') { - if (self.submit_index + SUBMIT_LOST < ledger_index) { - self.set_state('client_lost'); // Gave up. - self.emit('lost'); - self.callback('tejLost', message); - self.remote.removeListener('ledger_closed', on_ledger_closed); - self.emit('final', message); - self.finalized = true; - } else if (self.submit_index + SUBMIT_MISSING < ledger_index) { - self.set_state('client_missing'); // We don't know what happened to transaction, still might find. - self.emit('pending'); - } else { - self.emit('pending'); - } - } - // XXX Could log other unexpectedness. - }); - - transaction_entry.request(); - }; - - this.remote.on('ledger_closed', on_ledger_closed); - - this.once('error', function (message) { - self.callback(message.error, message); - }); - - this.set_state('client_submitted'); - - if (self.remote.local_sequence && !self.tx_json.Sequence) { - - self.tx_json.Sequence = this.remote.account_seq(self.tx_json.Account, 'ADVANCE'); - // console.log("Sequence: %s", self.tx_json.Sequence); - - if (!self.tx_json.Sequence) { - //console.log('NO SEQUENCE'); - - // Look in the last closed ledger. - var account_seq = this.remote.account_seq_cache(self.tx_json.Account, false) - - account_seq.on('success_account_seq_cache', function () { - // Try again. - self.submit(); - }) - - account_seq.on('error_account_seq_cache', function (message) { - // XXX Maybe be smarter about this. Don't want to trust an untrusted server for this seq number. - // Look in the current ledger. - self.remote.account_seq_cache(self.tx_json.Account, 'CURRENT') - .on('success_account_seq_cache', function () { - // Try again. - self.submit(); - }) - .on('error_account_seq_cache', function (message) { - // Forward errors. - self.emit('error', message); - }) - .request(); - }) - - account_seq.request(); - - return this; - } - - // If the transaction fails we want to either undo incrementing the sequence - // or submit a noop transaction to consume the sequence remotely. - this.once('success', function (res) { - if (typeof res.engine_result === 'string') { - switch (res.engine_result.slice(0, 3)) { - // Synchronous local error - case 'tej': - self.remote.account_seq(self.tx_json.Account, 'REWIND'); - break; - - case 'ter': - // XXX: What do we do in case of ter? - break; - - case 'tel': - case 'tem': - case 'tef': - // XXX Once we have a transaction submission manager class, we can - // check if there are any other transactions pending. If there are, - // we should submit a dummy transaction to ensure those - // transactions are still valid. - //var noop = self.remote.transaction().account_set(self.tx_json.Account); - //noop.submit(); - - // XXX Hotfix. This only works if no other transactions are pending. - self.remote.account_seq(self.tx_json.Account, 'REWIND'); - break; - } - } - }); - } - - // Prepare request - var request = this.remote.request_submit(); - - // Forward events - request.emit = this.emit.bind(this); - - if (!this._secret && !this.tx_json.Signature) { - finish({ - 'result' : 'tejSecretUnknown', - 'result_message' : "Could not sign transactions because we." - }); - return this; - } else if (this.remote.local_signing) { - this.sign(); - request.tx_blob(this.serialize().to_hex()); - } else { - if (!this.remote.trusted) { - finish({ - 'result' : 'tejServerUntrusted', - 'result_message' : "Attempt to give a secret to an untrusted server." - }); - } - - request.secret(this._secret); - request.build_path(this._build_path); - request.tx_json(this.tx_json); - } - - request.request(); - - return this; -} // // Set options for Transactions @@ -453,7 +246,7 @@ Transaction.prototype.build_path = function (build) { // tag should be undefined or a 32 bit integer. // YYY Add range checking for tag. Transaction.prototype.destination_tag = function (tag) { - if (tag !== undefined) { + if (tag !== void(0)) { this.tx_json.DestinationTag = tag; } @@ -463,9 +256,9 @@ Transaction.prototype.destination_tag = function (tag) { Transaction._path_rewrite = function (path) { var path_new = []; - for (var i = 0, l = path.length; i < l; i++) { - var node = path[i]; - var node_new = {}; + for (var i=0, l=path.length; i paths: undefined or array of path // A path is an array of objects containing some combination of: account, currency, issuer Transaction.prototype.paths = function (paths) { - for (var i = 0, l = paths.length; i < l; i++) { + for (var i=0, l=paths.length; i expiration : if not undefined, Date or Number // --> cancel_sequence : if not undefined, Sequence Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration, cancel_sequence) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'OfferCreate'; - this.tx_json.Account = UInt160.json_rewrite(src); - this.tx_json.TakerPays = Amount.json_rewrite(taker_pays); - this.tx_json.TakerGets = Amount.json_rewrite(taker_gets); + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'OfferCreate'; + this.tx_json.Account = UInt160.json_rewrite(src); + this.tx_json.TakerPays = Amount.json_rewrite(taker_pays); + this.tx_json.TakerGets = Amount.json_rewrite(taker_gets); + + if (this.remote.local_fee) { + this.tx_json.Fee = Transaction.fees.offer.to_json(); + } if (expiration) { this.tx_json.Expiration = expiration instanceof Date @@ -630,20 +427,20 @@ Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expi }; Transaction.prototype.password_fund = function (src, dst) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'PasswordFund'; - this.tx_json.Destination = UInt160.json_rewrite(dst); + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'PasswordFund'; + this.tx_json.Destination = UInt160.json_rewrite(dst); return this; } Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'PasswordSet'; - this.tx_json.RegularKey = authorized_key; - this.tx_json.Generator = generator; - this.tx_json.PublicKey = public_key; - this.tx_json.Signature = signature; + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'PasswordSet'; + this.tx_json.RegularKey = authorized_key; + this.tx_json.Generator = generator; + this.tx_json.PublicKey = public_key; + this.tx_json.Signature = signature; return this; } @@ -666,19 +463,19 @@ Transaction.prototype.password_set = function (src, authorized_key, generator, p // .set_flags() // .source_tag() Transaction.prototype.payment = function (src, dst, deliver_amount) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'Payment'; - this.tx_json.Account = UInt160.json_rewrite(src); - this.tx_json.Amount = Amount.json_rewrite(deliver_amount); - this.tx_json.Destination = UInt160.json_rewrite(dst); + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'Payment'; + this.tx_json.Account = UInt160.json_rewrite(src); + this.tx_json.Amount = Amount.json_rewrite(deliver_amount); + this.tx_json.Destination = UInt160.json_rewrite(dst); return this; } Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'TrustSet'; - this.tx_json.Account = UInt160.json_rewrite(src); + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'TrustSet'; + this.tx_json.Account = UInt160.json_rewrite(src); // Allow limit of 0 through. if (limit !== undefined) @@ -706,20 +503,49 @@ Transaction.prototype.wallet_add = function (src, amount, authorized_key, public return this; }; -/** - * Returns the number of fee units this transaction will cost. - * - * Each Ripple transaction based on its type and makeup costs a certain number - * of fee units. The fee units are calculated on a per-server basis based on the - * current load on both the network and the server. - * - * @see https://ripple.com/wiki/Transaction_Fee - */ -Transaction.prototype.fee_units = function () -{ - return Transaction.fees["default"]; -}; +// Submit a transaction to the network. +// XXX Don't allow a submit without knowing ledger_index. +// XXX Have a network canSubmit(), post events for following. +// XXX Also give broader status for tracking through network disconnects. +// callback = function (status, info) { +// // status is final status. Only works under a ledger_accepting conditions. +// switch status: +// case 'tesSUCCESS': all is well. +// case 'tejSecretUnknown': unable to sign transaction - secret unknown +// case 'tejServerUntrusted': sending secret to untrusted server. +// case 'tejInvalidAccount': locally detected error. +// case 'tejLost': locally gave up looking +// default: some other TER +// } -exports.Transaction = Transaction; +Transaction.prototype.submit = function (callback) { + var self = this; + + this.callback = (typeof callback === 'function') ? callback : function(){}; + + this.once('error', function transaction_error(error, message) { + self.callback(error, message); + }); + + this.once('success', function transaction_success(message) { + self.callback(null, message); + }); + + var account = this.tx_json.Account; + + if (typeof account !== 'string') { + this.emit('error', { + error: 'tejInvalidAccount', + error_message: 'Account is unspecified' + }); + } else { + // YYY Might check paths for invalid accounts. + this.remote.get_account(account).submit(this); + } + + return this; +} + +exports.Transaction = Transaction; // vim:sw=2:sts=2:ts=8:et From c06c74e46076fb021ebb541637dfe3acc7513df2 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 31 Jul 2013 19:54:07 -0700 Subject: [PATCH 05/30] Basic implementation for new path finding API. --- src/js/ripple/pathfind.js | 78 +++++++++++++++++++++++++++++++++++++++ src/js/ripple/remote.js | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 src/js/ripple/pathfind.js diff --git a/src/js/ripple/pathfind.js b/src/js/ripple/pathfind.js new file mode 100644 index 00000000..6dba8579 --- /dev/null +++ b/src/js/ripple/pathfind.js @@ -0,0 +1,78 @@ +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + +var Amount = require('./amount').Amount; + +var extend = require('extend'); + +/** + * Represents a persistent path finding request. + * + * Only one path find request is allowed per connection, so when another path + * find request is triggered it will supercede the existing one, making it emit + * the 'end' and 'superceded' events. + */ +var PathFind = function (remote, src_account, dst_account, + dst_amount, src_currencies) +{ + EventEmitter.call(this); + + this.remote = remote; + + this.src_account = src_account; + this.dst_account = dst_account; + this.dst_amount = dst_amount; + this.src_currencies = src_currencies; +}; + +util.inherits(PathFind, EventEmitter); + +/** + * Submits a path_find_create request to the network. + * + * This starts a path find request, superceding all previous path finds. + * + * This will be called automatically by Remote when this object is instantiated, + * so you should only have to call it if the path find was closed or superceded + * and you wish to restart it. + */ +PathFind.prototype.create = function () +{ + var req = this.remote.request_path_find_create(this.src_account, + this.dst_account, + this.dst_amount, + this.src_currencies); + + req.request(); +}; + +PathFind.prototype.close = function () +{ + this.remote.request_path_find_close().request(); + this.emit('end'); + this.emit('close'); +}; + +PathFind.prototype.notify_update = function (message) +{ + var src_account = message.source_account; + var dst_account = message.destination_account; + var dst_amount = Amount.from_json(message.destination_amount); + + // Only pass the event along if this path find response matches what we were + // looking for. + if (message.alternatives && message.alternatives.length) console.log(Amount.from_json(message.alternatives[0].source_amount).to_text_full()); + if (this.src_account === src_account && + this.dst_account === dst_account && + this.dst_amount.equals(dst_amount)) { + this.emit('update', message); + } +}; + +PathFind.prototype.notify_superceded = function () +{ + this.emit('end'); + this.emit('superceded'); +}; + +exports.PathFind = PathFind; diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index 6606e7d5..0d11a8a7 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -26,6 +26,7 @@ var Transaction = require('./transaction').Transaction; var Account = require('./account').Account; var Meta = require('./meta').Meta; var OrderBook = require('./orderbook').OrderBook; +var PathFind = require('./pathfind').PathFind; var utils = require('./utils'); var config = require('./config'); @@ -348,6 +349,7 @@ function Remote(opts, trace) { this._connected = false; this._last_tx = null; + this._cur_path_find = null; // Local signing implies local fees and sequences if (this.local_signing) { @@ -655,6 +657,15 @@ Remote.prototype._handle_message = function (json) { this.emit('transaction_all', message); break; + case 'path_find': + // Pass the event to the currently open PathFind object + if (this._cur_path_find) { + this._cur_path_find.notify_update(message); + } + + this.emit('path_find_all', message); + break; + // XXX Should be tracked by the Server object case 'serverStatus': if ('load_base' in message && 'load_factor' in message && @@ -1214,6 +1225,24 @@ Remote.prototype.account = function (accountId, callback) { return account; }; +Remote.prototype.path_find = function (src_account, dst_account, + dst_amount, src_currencies) +{ + var path_find = new PathFind(this, + src_account, dst_account, + dst_amount, src_currencies); + + if (this._cur_path_find) { + this._cur_path_find.notify_superceded(); + } + + path_find.create(); + + this._cur_path_find = path_find; + + return path_find; +}; + Remote.prototype.book = function (currency_gets, issuer_gets, currency_pays, issuer_pays) { var gets = currency_gets; @@ -1385,6 +1414,47 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, return request; }; +Remote.prototype.request_path_find_create = function (src_account, dst_account, + dst_amount, + src_currencies, callback) +{ + var self = this; + var request = new Request(this, 'path_find'); + + request.message.subcommand = 'create'; + request.message.source_account = UInt160.json_rewrite(src_account); + request.message.destination_account = UInt160.json_rewrite(dst_account); + request.message.destination_amount = Amount.json_rewrite(dst_amount); + + if (src_currencies) { + request.message.source_currencies = src_currencies.map(function (ci) { + var ci_new = {}; + + if ('issuer' in ci) + ci_new.issuer = UInt160.json_rewrite(ci.issuer); + + if ('currency' in ci) + ci_new.currency = Currency.json_rewrite(ci.currency); + + return ci_new; + }); + } + + request.callback(callback); + + return request; +}; + +Remote.prototype.request_path_find_close = function () +{ + var self = this; + var request = new Request(this, 'path_find'); + + request.message.subcommand = 'close'; + + return request; +}; + Remote.prototype.request_unl_list = function (callback) { var request = new Request(this, 'unl_list'); request.callback(callback); From 8294c78db4a9b8f332f48938443a4006e536452b Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Fri, 2 Aug 2013 08:17:14 +0900 Subject: [PATCH 06/30] Greater convenience for payment transactions --- src/js/ripple/remote.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index 0754791f..a6aebc12 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -80,7 +80,8 @@ function Remote(opts, trace) { ? true : Boolean(opts.local_signing); this.fee_cushion = (typeof opts.fee_cushion === 'undefined') ? 1.5 : Number(opts.fee_cushion); - + this.max_fee = (typeof opts.max_fee === 'undefined') + ? Infinity: Number(opts.max_fee); this.id = 0; this.trace = opts.trace || trace; this._server_fatal = false; // True, if we know server exited. @@ -1207,9 +1208,17 @@ Remote.prototype.request_connect = function (ip, port, callback) { return request; }; -Remote.prototype.create_transaction = -Remote.prototype.transaction = function () { - return new Transaction(this); +Remote.prototype.transaction = function (source, destination, amount, callback) { + var tx = new Transaction(this); + + if (arguments.length >= 3) { + tx = tx.payment(source, destination, amount); + if (typeof callback === 'function') { + tx.submit(callback); + } + } + + return tx; }; /** From b78835f195353456f5f1a3a40b7c5017891c71bc Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Sat, 3 Aug 2013 07:18:02 +0900 Subject: [PATCH 07/30] Typo --- src/js/ripple/remote.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index d65044df..c158f728 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -199,10 +199,10 @@ util.inherits(Remote, EventEmitter); // Flags for ledger entries. In support of account_root(). Remote.flags = { account_root : { - PasswordSpent: 0x00010000, - RequireDestTag: 0x00020000, - RequireAuth: 0x00040000, - DisallowXRP: 0x00080000 + PasswordSpent: 0x00010000, + RequireDestTag: 0x00020000, + RequireAuth: 0x00040000, + DisallowXRP: 0x00080000 } }; @@ -423,18 +423,16 @@ Remote.prototype._handle_message = function (json) { message.mmeta = new Meta(message.meta); // Pass the event on to any related Account objects - var affected = message.mmeta.getAffectedAccounts(); - for (var i=0, l=affected.length; i Date: Sat, 3 Aug 2013 07:19:15 +0900 Subject: [PATCH 08/30] Update untracked files --- src/js/ripple/request.js | 253 ++++++++++++++++++++++++ src/js/ripple/transactionmanager.js | 287 ++++++++++++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 src/js/ripple/request.js create mode 100644 src/js/ripple/transactionmanager.js diff --git a/src/js/ripple/request.js b/src/js/ripple/request.js new file mode 100644 index 00000000..651b89e7 --- /dev/null +++ b/src/js/ripple/request.js @@ -0,0 +1,253 @@ +var EventEmitter = require('events').EventEmitter; +var util = require('util'); +var UInt160 = require('./uint160').UInt160; +var Currency = require('./currency').Currency; +var Transaction = require('./transaction').Transaction; +var Account = require('./account').Account; +var Meta = require('./meta').Meta; +var OrderBook = require('./orderbook').OrderBook; + +// Request events emitted: +// 'success' : Request successful. +// 'error' : Request failed. +// 'remoteError' +// 'remoteUnexpected' +// 'remoteDisconnected' +function Request(remote, command) { + EventEmitter.call(this); + this.remote = remote; + this.requested = false; + this.message = { + command : command, + id : void(0) + }; +}; + +util.inherits(Request, EventEmitter); + +// Send the request to a remote. +Request.prototype.request = function (remote) { + if (!this.requested) { + this.requested = true; + this.remote.request(this); + this.emit('request', remote); + } +}; + +Request.prototype.callback = function(callback, successEvent, errorEvent) { + if (callback && typeof callback === 'function') { + this.once(successEvent || 'success', callback.bind(this, null)); + this.once(errorEvent || 'error' , callback.bind(this)); + this.request(); + } + + return this; +}; + +Request.prototype.timeout = function(duration, callback) { + if (!this.requested) { + this.once('request', this.timeout.bind(this, duration, callback)); + return; + }; + + var self = this; + var emit = this.emit; + var timed_out = false; + + var timeout = setTimeout(function() { + timed_out = true; + if (typeof callback === 'function') callback(); + emit.call(self, 'timeout'); + }, duration); + + this.emit = function() { + if (timed_out) return; + else clearTimeout(timeout); + emit.apply(self, arguments); + }; + + return this; +}; + +Request.prototype.build_path = function (build) { + if (build) { + this.message.build_path = true; + } + + return this; +}; + +Request.prototype.ledger_choose = function (current) { + if (current) { + this.message.ledger_index = this.remote._ledger_current_index; + } else { + this.message.ledger_hash = this.remote._ledger_hash; + } + + return this; +}; + +// Set the ledger for a request. +// - ledger_entry +// - transaction_entry +Request.prototype.ledger_hash = function (h) { + this.message.ledger_hash = h; + + return this; +}; + +// Set the ledger_index for a request. +// - ledger_entry +Request.prototype.ledger_index = function (ledger_index) { + this.message.ledger_index = ledger_index; + + return this; +}; + +Request.prototype.ledger_select = function (ledger_spec) { + switch (ledger_spec) { + case 'current': + case 'closed': + case 'verified': + this.message.ledger_index = ledger_spec; + break; + + default: + // XXX Better test needed + if (String(ledger_spec).length > 12) { + this.message.ledger_hash = ledger_spec; + } else { + this.message.ledger_index = ledger_spec; + } + break; + } + + return this; +}; + +Request.prototype.account_root = function (account) { + this.message.account_root = UInt160.json_rewrite(account); + + return this; +}; + +Request.prototype.index = function (hash) { + this.message.index = hash; + + return this; +}; + +// Provide the information id an offer. +// --> account +// --> seq : sequence number of transaction creating offer (integer) +Request.prototype.offer_id = function (account, seq) { + this.message.offer = { + account: UInt160.json_rewrite(account), + seq: seq + }; + + return this; +}; + +// --> index : ledger entry index. +Request.prototype.offer_index = function (index) { + this.message.offer = index; + + return this; +}; + +Request.prototype.secret = function (s) { + if (s) { + this.message.secret = s; + } + + return this; +}; + +Request.prototype.tx_hash = function (h) { + this.message.tx_hash = h; + + return this; +}; + +Request.prototype.tx_json = function (j) { + this.message.tx_json = j; + + return this; +}; + +Request.prototype.tx_blob = function (j) { + this.message.tx_blob = j; + + return this; +}; + +Request.prototype.ripple_state = function (account, issuer, currency) { + this.message.ripple_state = { + 'accounts' : [ + UInt160.json_rewrite(account), + UInt160.json_rewrite(issuer) + ], + 'currency' : currency + }; + + return this; +}; + +Request.prototype.accounts = function (accounts, realtime) { + if (!Array.isArray(accounts)) { + accounts = [ accounts ]; + } + + // Process accounts parameters + var procAccounts = accounts.map(function(account) { + return UInt160.json_rewrite(account); + }); + + if (realtime) { + this.message.rt_accounts = procAccounts; + } else { + this.message.accounts = procAccounts; + } + + return this; +}; + +Request.prototype.rt_accounts = function (accounts) { + return this.accounts(accounts, true); +}; + +Request.prototype.books = function (books, snapshot) { + var procBooks = []; + + for (var i = 0, l = books.length; i < l; i++) { + var book = books[i]; + var json = {}; + + function processSide(side) { + if (!book[side]) throw new Error('Missing '+side); + + var obj = json[side] = { + currency: Currency.json_rewrite(book[side].currency) + }; + + if (obj.currency !== 'XRP') { + obj.issuer = UInt160.json_rewrite(book[side].issuer); + } + } + + processSide('taker_gets'); + processSide('taker_pays'); + + if (snapshot) json.snapshot = true; + if (book.both) json.both = true; + + procBooks.push(json); + } + + this.message.books = procBooks; + + return this; +}; + +exports.Request = Request; diff --git a/src/js/ripple/transactionmanager.js b/src/js/ripple/transactionmanager.js new file mode 100644 index 00000000..e0131b79 --- /dev/null +++ b/src/js/ripple/transactionmanager.js @@ -0,0 +1,287 @@ +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + +/** + * @constructor TransactionManager + * @param {Object} account + */ + +function TransactionManager(account) { + EventEmitter.call(this); + + var self = this; + + this.account = account; + this.remote = account._remote; + this._timeout = void(0); + this._next_sequence = void(0); + this._config = { max_fee: self.remote.max_fee }; + + function sequence_loaded(err, sequence) { + self._next_sequence = sequence; + self.emit('sequence_loaded', sequence); + } + + account.get_next_sequence(sequence_loaded); +} + +util.inherits(TransactionManager, EventEmitter); + +/** + * @param {Object} tx + */ + +//TransactionManager.prototype.update = function(tx) { +// this._next_sequence_number = Math.max(tx.meta.account_next_seq, this._next_sequence_number); +// var queued; +// +// if (queued = this.queue.get('hash', tx.hash)) { +// this.queue.remove('hash', queued.hash); +// } else if (queued = this.queue.get('sequence', tx.seq)) { +// if (queued.nullified) { +// //Figures out new sequence number, signs, and sends to network +// this.submit(queued); +// } +// } +//} + +/** + * User has given up, replace transaction with a null transaction + * + * @param {Object} tx + */ + +//TransactionManager.prototype._nullify = function(tx) { +// this.queue.remove('hash', tx.hash); +//} + +/** + */ + +//TransactionManager.prototype._timeout = function() { +// var transactions = this.queue.get(); +// for (var i=0; i this._config.maxFee) { +// if (this._timeout) clearTimeout(this._timeout); +// return; +// } +// +// // resubmit everything raising fee if needed +// for (var i=0; i= 8) { + // Lost + tx.emit('error', message); + tx.emit('lost', message); + } else if (dif >= 4) { + // Missing + tx.set_state('client_missing'); + tx.emit('pending', message); + } else { + // Pending + tx.emit('pending', message); + } + } else { + // Transaction was found in the ledger, + // consider this transaction successful + if (message.metadata) { + tx.set_state(message.metadata.TransactionResult); + } + tx.emit('success', message); + } + }); + } + + function transaction_proposed() { + // Check the ledger for transaction entry + remote.addListener('ledger_closed', ledger_closed); + } + + function transaction_finalized() { + // Stop checking the ledger + remote.removeListener('ledger_closed', ledger_closed); + } + + tx.once('proposed', transaction_proposed); + tx.once('final', transaction_finalized); +} + +/** + * @param {Object} tx + */ + +TransactionManager.prototype.submit = function(tx) { + // If sequence number is not yet known, defer until it is. + if (!this._next_sequence) { + this.once('sequence_loaded', this.submit.bind(this, tx)); + return; + } + + var seq = this._next_sequence++; + var fee = tx.fee_units(); + + if (fee <= this._config.max_fee) { + tx.tx_json.Sequence = seq; + tx.tx_json.Fee = fee; + tx.complete(); + + this._request(tx); + } +} + +exports.TransactionManager = TransactionManager; From d870264230e3f4f86ffaee9b71b408c8dee36d00 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Sat, 3 Aug 2013 07:23:12 +0900 Subject: [PATCH 09/30] Update untracked files --- src/js/ripple/account.js | 3 +-- src/js/ripple/transaction.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/js/ripple/account.js b/src/js/ripple/account.js index f92296f4..b9425d61 100644 --- a/src/js/ripple/account.js +++ b/src/js/ripple/account.js @@ -19,7 +19,6 @@ var Amount = require('./amount').Amount; var UInt160 = require('./uint160').UInt160; var TransactionManager = require('./transactionmanager').TransactionManager; - function Account(remote, account) { EventEmitter.call(this); @@ -83,7 +82,7 @@ util.inherits(Account, EventEmitter); /** * List of events that require a remote subscription to the account. */ -Account.subscribe_events = ['transaction', 'entry']; +Account.subscribe_events = [ 'transaction', 'entry' ]; Account.prototype.to_json = function () { return this._account.to_json(); diff --git a/src/js/ripple/transaction.js b/src/js/ripple/transaction.js index 27aef18f..26398a68 100644 --- a/src/js/ripple/transaction.js +++ b/src/js/ripple/transaction.js @@ -181,7 +181,7 @@ Transaction.prototype.get_fee = function() { Transaction.prototype.complete = function () { var tx_json = this.tx_json; - if (typeof tx_json.Fee === 'undefined' && this.remote.local_fee === void(0)) { + if (typeof tx_json.Fee === 'undefined' && this.remote.local_fee) { this.tx_json.Fee = this.remote.fee_tx(this.fee_units()).to_json(); } @@ -409,7 +409,7 @@ Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expi this.tx_json.TakerGets = Amount.json_rewrite(taker_gets); if (this.remote.local_fee) { - this.tx_json.Fee = Transaction.fees.offer.to_json(); + //this.tx_json.Fee = Transaction.fees.offer.to_json(); } if (expiration) { @@ -537,13 +537,16 @@ Transaction.prototype.submit = function (callback) { this.callback = typeof callback === 'function' ? callback : function(){}; - this.once('error', function(error, message) { + function submission_error(error, message) { self.callback(error, message); - }); + } - this.once('success', function(message) { + function submission_success(message) { self.callback(null, message); - }); + } + + this.once('error', submission_error); + this.once('success', submission_success); var account = this.tx_json.Account; From bb6ec3b5c99c8ab3401fffb7826ba96eb7c07990 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Sat, 3 Aug 2013 07:27:59 +0900 Subject: [PATCH 10/30] Replace comment --- src/js/ripple/transactionmanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/ripple/transactionmanager.js b/src/js/ripple/transactionmanager.js index e0131b79..77dec187 100644 --- a/src/js/ripple/transactionmanager.js +++ b/src/js/ripple/transactionmanager.js @@ -157,8 +157,8 @@ TransactionManager.prototype._request = function(tx) { result_message: message.engine_result_message, engine_result_message: message.engine_result_message, - rejected: tx.isRejected(message.engine_result_code), // If server is honest, don't expect a final if rejected. + rejected: tx.isRejected(message.engine_result_code), }); } } From 5f017b2b85c847688235f7af8dcad94caa83ac6f Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Sat, 3 Aug 2013 08:13:36 +0900 Subject: [PATCH 11/30] Cleanup, fix jshint warnings --- src/js/ripple/remote.js | 201 ++++++++++++++++++++++------------------ 1 file changed, 109 insertions(+), 92 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index c158f728..21872a22 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -80,12 +80,9 @@ function Remote(opts, trace) { this.trusted = opts.trusted; this.local_sequence = opts.local_sequence; // Locally track sequence numbers this.local_fee = opts.local_fee; // Locally set fees - this.local_signing = (typeof opts.local_signing === 'undefined') - ? true : Boolean(opts.local_signing); - this.fee_cushion = (typeof opts.fee_cushion === 'undefined') - ? 1.5 : Number(opts.fee_cushion); - this.max_fee = (typeof opts.max_fee === 'undefined') - ? Infinity: Number(opts.max_fee); + this.local_signing = (typeof opts.local_signing === 'undefined') ? true : Boolean(opts.local_signing); + this.fee_cushion = (typeof opts.fee_cushion === 'undefined') ? 1.5 : Number(opts.fee_cushion); + this.max_fee = (typeof opts.max_fee === 'undefined') ? Infinity : Number(opts.max_fee); this.id = 0; this.trace = opts.trace || trace; this._server_fatal = false; // True, if we know server exited. @@ -161,7 +158,7 @@ function Remote(opts, trace) { secure: opts.websocket_ssl, trusted: opts.trusted } - ] + ]; } opts.servers.forEach(function(server) { @@ -192,7 +189,7 @@ function Remote(opts, trace) { } } }); -}; +} util.inherits(Remote, EventEmitter); @@ -211,7 +208,7 @@ Remote.from_config = function (obj, trace) { var remote = new Remote(serverConfig, trace); - for (var account in config.accounts) { + Object.keys(config.accounts).forEach(function(account) { var accountInfo = config.accounts[account]; if (typeof accountInfo === 'object') { if (accountInfo.secret) { @@ -221,7 +218,7 @@ Remote.from_config = function (obj, trace) { remote.set_secret(accountInfo.account, accountInfo.secret); } } - } + }); return remote; }; @@ -243,9 +240,9 @@ var isTefFailure = function (engine_result_code) { Remote.prototype.add_server = function (opts) { var self = this; - var url = ((opts.secure || opts.websocket_ssl) ? 'wss://' : 'ws://') - + (opts.host || opts.websocket_ip) + ':' - + (opts.port || opts.websocket_port) + var url = ((opts.secure || opts.websocket_ssl) ? 'wss://' : 'ws://') + + (opts.host || opts.websocket_ip) + ':' + + (opts.port || opts.websocket_port) ; var server = new Server(this, {url: url}); @@ -284,7 +281,9 @@ Remote.prototype.server_fatal = function () { // Set the emitted state: 'online' or 'offline' Remote.prototype._set_state = function (state) { - if (this.trace) console.log('remote: set_state: %s', state); + if (this.trace) { + console.log('remote: set_state: %s', state); + } if (this.state !== state) { this.state = state; @@ -328,8 +327,9 @@ Remote.prototype.connect = function (online) { break; default: - if (!Boolean(online)) - return this.disconnect() + if (!Boolean(online)) { + return this.disconnect(); + } break; } @@ -371,7 +371,7 @@ Remote.prototype.ledger_hash = function () { Remote.prototype._handle_message = function (json) { var self = this; var unexpected = false; - var message + var message; try { message = JSON.parse(json); @@ -414,24 +414,33 @@ Remote.prototype._handle_message = function (json) { // De-duplicate transactions that are immediately following each other // XXX Should have a cache of n txs so we can dedup out of order txs - if (this._last_tx === message.transaction.hash) break; + if (this._last_tx === message.transaction.hash) { + break; + } + this._last_tx = message.transaction.hash; - if (this.trace) utils.logObject('remote: tx: %s', message); + if (this.trace) { + utils.logObject('remote: tx: %s', message); + } // Process metadata message.mmeta = new Meta(message.meta); // Pass the event on to any related Account objects message.mmeta.getAffectedAccounts().forEach(function(account) { - var account = self._accounts[account]; - if (account) account.notifyTx(message); + account = self._accounts[account]; + if (account) { + account.notifyTx(message); + } }); // Pass the event on to any related OrderBooks message.mmeta.getAffectedBooks().forEach(function(book) { - var book = self._books[book]; - if (book) book.notifyTx(message); + book = self._books[book]; + if (book) { + book.notifyTx(message); + } }); this.emit('transaction', message); @@ -451,18 +460,23 @@ Remote.prototype._handle_message = function (json) { case 'serverStatus': self.emit('server_status', message); - if ('load_base' in message && 'load_factor' in message && - (message.load_base !== self._load_base || message.load_factor != self._load_factor)) - { - self._load_base = message.load_base; - self._load_factor = message.load_factor; - self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); - } + var load_changed = message.hasOwnProperty('load_base') + && message.hasOwnProperty('load_factor') + && (message.load_base !== self._load_base || message.load_factor !== self._load_factor) + ; + + if (load_changed) { + self._load_base = message.load_base; + self._load_factor = message.load_factor; + self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); + } break; // All other messages default: - if (this.trace) utils.logObject('remote: '+message.type+': %s', message); + if (this.trace) { + utils.logObject('remote: '+message.type+': %s', message); + } this.emit('net_' + message.type, message); break; } @@ -501,7 +515,9 @@ Remote.prototype._get_server = function () { server = this._primary_server; } else { server = this._next_server(); - if (server) this._set_primary_server(server); + if (server) { + this._set_primary_server(server); + } } return server; @@ -541,12 +557,14 @@ Remote.prototype.request_ledger = function (ledger, opts, callback) { request.message.ledger = ledger; } - switch(typeof opts) { + switch (typeof opts) { case 'object': - if (opts.full) request.message.full = true; - if (opts.expand) request.message.expand = true; - if (opts.transactions) request.message.transactions = true; - if (opts.accounts) request.message.accounts = true; + var valid_properties = [ 'full', 'expand', 'transactions', 'accounts' ]; + valid_properties.forEach(function(prop) { + if (opts.hasOwnProperty(prop)) { + request.message[prop] = true; + } + }); break; case 'function': @@ -602,21 +620,21 @@ Remote.prototype.request_ledger_entry = function (type, callback) { if (type === 'account_root') { request.request_default = request.request; - request.request = function () { // Intercept default request. + request.request = function () { // Intercept default request. var bDefault = true; // .self = Remote // this = Request // console.log('request_ledger_entry: caught'); - if (self._ledger_hash) { + //if (self._ledger_hash) { // A specific ledger is requested. - // XXX Add caching. - } - // else if (req.ledger_index) - // else if ('ripple_state' === request.type) // YYY Could be cached per ledger. - else if (type === 'account_root') { + // else if (req.ledger_index) + // else if ('ripple_state' === request.type) // YYY Could be cached per ledger. + //} + + if (!self._ledger_hash && type === 'account_root') { var cache = self.ledgers.current.account_root; if (!cache) { @@ -630,12 +648,11 @@ Remote.prototype.request_ledger_entry = function (type, callback) { // console.log('request_ledger_entry: emulating'); request.emit('success', { // YYY Missing lots of fields. - 'node' : node, + node : node }); bDefault = false; } else { // Was not cached. - // XXX Only allow with trusted mode. Must sync response with advance. switch (type) { case 'account_root': @@ -657,8 +674,8 @@ Remote.prototype.request_ledger_entry = function (type, callback) { // console.log('request_ledger_entry: invoking'); request.request_default(); } - } - }; + }; + } request.callback(callback); @@ -779,20 +796,15 @@ Remote.prototype.request_account_tx = function (obj, callback) { var request = new Request(this, 'account_tx'); - request.message.account = obj.account; + request.message.account = obj.account; - if (false && ledger_min === ledger_max) { - //request.message.ledger = ledger_min; - } - else { - if (typeof obj.ledger_index_min !== 'undefined') {request.message.ledger_index_min = obj.ledger_index_min;} - if (typeof obj.ledger_index_max !== 'undefined') {request.message.ledger_index_max = obj.ledger_index_max;} - if (typeof obj.binary !== 'undefined') {request.message.binary = obj.binary;} - if (typeof obj.count !== 'undefined') {request.message.count = obj.count;} - if (typeof obj.descending !== 'undefined') {request.message.descending = obj.descending;} - if (typeof obj.offset !== 'undefined') {request.message.offset = obj.offset;} - if (typeof obj.limit !== 'undefined') {request.message.limit = obj.limit;} - } + if (typeof obj.ledger_index_min !== 'undefined') {request.message.ledger_index_min = obj.ledger_index_min;} + if (typeof obj.ledger_index_max !== 'undefined') {request.message.ledger_index_max = obj.ledger_index_max;} + if (typeof obj.binary !== 'undefined') {request.message.binary = obj.binary;} + if (typeof obj.count !== 'undefined') {request.message.count = obj.count;} + if (typeof obj.descending !== 'undefined') {request.message.descending = obj.descending;} + if (typeof obj.offset !== 'undefined') {request.message.offset = obj.offset;} + if (typeof obj.limit !== 'undefined') {request.message.limit = obj.limit;} request.callback(callback); @@ -944,7 +956,7 @@ Remote.prototype.request_account_balance = function (account, current, callback) .on('success', function (message) { // If the caller also waits for 'success', they might run before this. request.emit('account_balance', Amount.from_json(message.node.Balance)); - }) + }); request.callback(callback, 'account_balance'); @@ -960,7 +972,7 @@ Remote.prototype.request_account_flags = function (account, current, callback) { .on('success', function (message) { // If the caller also waits for 'success', they might run before this. request.emit('account_flags', message.node.Flags); - }) + }); request.callback(callback, 'account_flags'); @@ -976,7 +988,7 @@ Remote.prototype.request_owner_count = function (account, current, callback) { .on('success', function (message) { // If the caller also waits for 'success', they might run before this. request.emit('owner_count', message.node.OwnerCount); - }) + }); request.callback(callback, 'owner_count'); @@ -990,19 +1002,17 @@ Remote.prototype.account = function (accountId) { if (!account) { account = new Account(this, accountId); - if (!account.is_valid()) return account; + if (!account.is_valid()) { + return account; + } this._accounts[accountId] = account; } return account; }; -Remote.prototype.path_find = function (src_account, dst_account, - dst_amount, src_currencies) -{ - var path_find = new PathFind(this, - src_account, dst_account, - dst_amount, src_currencies); +Remote.prototype.path_find = function (src_account, dst_account, dst_amount, src_currencies) { + var path_find = new PathFind(this, src_account, dst_account, dst_amount, src_currencies); if (this._cur_path_find) { this._cur_path_find.notify_superceded(); @@ -1015,12 +1025,16 @@ Remote.prototype.path_find = function (src_account, dst_account, return path_find; }; -Remote.prototype.book = function (currency_gets, issuer_gets, - currency_pays, issuer_pays) { +Remote.prototype.book = function (currency_gets, issuer_gets, currency_pays, issuer_pays) { var gets = currency_gets; - if (gets !== 'XRP') gets += '/' + issuer_gets; + if (gets !== 'XRP') { + gets += '/' + issuer_gets; + } + var pays = currency_pays; - if (pays !== 'XRP') pays += '/' + issuer_pays; + if (pays !== 'XRP') { + pays += '/' + issuer_pays; + } var key = gets + ':' + pays; @@ -1030,13 +1044,15 @@ Remote.prototype.book = function (currency_gets, issuer_gets, currency_pays, issuer_pays ); - if (!book.is_valid()) return book; + if (!book.is_valid()) { + return book; + } this._books[key] = book; } return this._books[key]; -} +}; // Return the next account sequence if possible. // <-- undefined or Sequence @@ -1048,16 +1064,19 @@ Remote.prototype.account_seq = function (account, advance) { if (account_info && account_info.seq) { seq = account_info.seq; - if (advance === 'ADVANCE') account_info.seq += 1; - if (advance === 'REWIND') account_info.seq -= 1; + if (advance === 'ADVANCE') { + account_info.seq += 1; + } + + if (advance === 'REWIND') { + account_info.seq -= 1; + } // console.log('cached: %s current=%d next=%d', account, seq, account_info.seq); - } else { - // console.log('uncached: %s', account); } return seq; -} +}; Remote.prototype.set_account_seq = function (account, seq) { var account = UInt160.json_rewrite(account); @@ -1098,7 +1117,7 @@ Remote.prototype.account_seq_cache = function (account, current, callback) { request.emit('error_account_seq_cache', message); }); - account_info.caching_seq_request = request; + account_info.caching_seq_request = request; } request.callback(callback, 'success_account_seq_cache', 'error_account_seq_cache'); @@ -1189,10 +1208,7 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, return request; }; -Remote.prototype.request_path_find_create = function (src_account, dst_account, - dst_amount, - src_currencies, callback) -{ +Remote.prototype.request_path_find_create = function (src_account, dst_account, dst_amount, src_currencies, callback) { var self = this; var request = new Request(this, 'path_find'); @@ -1205,11 +1221,13 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account, request.message.source_currencies = src_currencies.map(function (ci) { var ci_new = {}; - if ('issuer' in ci) - ci_new.issuer = UInt160.json_rewrite(ci.issuer); + if ('issuer' in ci) { + ci_new.issuer = UInt160.json_rewrite(ci.issuer); + } - if ('currency' in ci) + if ('currency' in ci) { ci_new.currency = Currency.json_rewrite(ci.currency); + } return ci_new; }); @@ -1220,8 +1238,7 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account, return request; }; -Remote.prototype.request_path_find_close = function () -{ +Remote.prototype.request_path_find_close = function () { var self = this; var request = new Request(this, 'path_find'); From ab5d64ff12a9477c01b6f94bcb3df935f355550f Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Sat, 3 Aug 2013 09:01:55 +0900 Subject: [PATCH 12/30] Update repository URL --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 578dd90f..a3d52557 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/rippleFoundation/ripple-lib.git" + "url": "git://github.com/ripple/ripple-lib.git" }, "readmeFilename": "README.md", "engines": { From eac195ec180d8ccf80b13b43be9629c0f05f53ba Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Tue, 6 Aug 2013 04:20:05 +0900 Subject: [PATCH 13/30] Cleanup --- src/js/ripple/remote.js | 127 +++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index 21872a22..2a55652b 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -40,16 +40,17 @@ var sjcl = require('../../../build/sjcl'); Keys for opts: - trusted : truthy, if remote is trusted trace - maxListeners - fee_cushion : Extra fee multiplier to account for async fee changes. - servers : Array of server objects with the following form + max_listeners : Set maxListeners for remote; prevents EventEmitter warnings + trusted : truthy, if remote is trusted + max_fee : Maximum acceptable transaction fee + fee_cushion : Extra fee multiplier to account for async fee changes. + servers : Array of server objects with the following form { - host: , - port: , - secure: + host: + , port: + , secure: } Events: @@ -167,7 +168,7 @@ function Remote(opts, trace) { }); // This is used to remove Node EventEmitter warnings - var maxListeners = opts.maxListeners || 0; + var maxListeners = opts.maxListeners || opts.max_listeners || 0; this._servers.concat(this).forEach(function(emitter) { emitter.setMaxListeners(maxListeners); }); @@ -646,11 +647,10 @@ Remote.prototype.request_ledger_entry = function (type, callback) { if (node) { // Emulate fetch of ledger entry. // console.log('request_ledger_entry: emulating'); + // YYY Missing lots of fields. request.emit('success', { - // YYY Missing lots of fields. node : node }); - bDefault = false; } else { // Was not cached. // XXX Only allow with trusted mode. Must sync response with advance. @@ -798,13 +798,21 @@ Remote.prototype.request_account_tx = function (obj, callback) { request.message.account = obj.account; - if (typeof obj.ledger_index_min !== 'undefined') {request.message.ledger_index_min = obj.ledger_index_min;} - if (typeof obj.ledger_index_max !== 'undefined') {request.message.ledger_index_max = obj.ledger_index_max;} - if (typeof obj.binary !== 'undefined') {request.message.binary = obj.binary;} - if (typeof obj.count !== 'undefined') {request.message.count = obj.count;} - if (typeof obj.descending !== 'undefined') {request.message.descending = obj.descending;} - if (typeof obj.offset !== 'undefined') {request.message.offset = obj.offset;} - if (typeof obj.limit !== 'undefined') {request.message.limit = obj.limit;} + var props = [ + 'ledger_index_min' + , 'ledger_index_max' + , 'binary' + , 'count' + , 'descending' + , 'offset' + , 'limit' + ]; + + props.forEach(function(prop) { + if (obj.hasOwnProperty(prop)) { + request.message[prop] = obj[prop]; + } + }); request.callback(callback); @@ -852,8 +860,9 @@ Remote.prototype.request_sign = function (secret, tx_json, callback) { var request = new Request(this, 'sign'); - request.message.secret = secret; + request.message.secret = secret; request.message.tx_json = tx_json; + request.callback(callback); return request; @@ -925,7 +934,6 @@ Remote.prototype._server_prepare_subscribe = function (callback) { request.callback(callback); - // XXX Could give error events, maybe even time out. return request; @@ -951,12 +959,12 @@ Remote.prototype.ledger_accept = function (callback) { Remote.prototype.request_account_balance = function (account, current, callback) { var request = this.request_ledger_entry('account_root'); - request.account_root(account) - .ledger_choose(current) - .on('success', function (message) { - // If the caller also waits for 'success', they might run before this. - request.emit('account_balance', Amount.from_json(message.node.Balance)); - }); + request.account_root(account); + request.ledger_choose(current); + request.once('success', function (message) { + // If the caller also waits for 'success', they might run before this. + request.emit('account_balance', Amount.from_json(message.node.Balance)); + }); request.callback(callback, 'account_balance'); @@ -967,12 +975,12 @@ Remote.prototype.request_account_balance = function (account, current, callback) Remote.prototype.request_account_flags = function (account, current, callback) { var request = this.request_ledger_entry('account_root'); - request.account_root(account) - .ledger_choose(current) - .on('success', function (message) { - // If the caller also waits for 'success', they might run before this. - request.emit('account_flags', message.node.Flags); - }); + request.account_root(account); + request.ledger_choose(current); + request.on('success', function (message) { + // If the caller also waits for 'success', they might run before this. + request.emit('account_flags', message.node.Flags); + }); request.callback(callback, 'account_flags'); @@ -983,12 +991,12 @@ Remote.prototype.request_account_flags = function (account, current, callback) { Remote.prototype.request_owner_count = function (account, current, callback) { var request = this.request_ledger_entry('account_root'); - request.account_root(account) - .ledger_choose(current) - .on('success', function (message) { - // If the caller also waits for 'success', they might run before this. - request.emit('owner_count', message.node.OwnerCount); - }); + request.account_root(account); + request.ledger_choose(current); + request.on('success', function (message) { + // If the caller also waits for 'success', they might run before this. + request.emit('owner_count', message.node.OwnerCount); + }); request.callback(callback, 'owner_count'); @@ -1097,25 +1105,27 @@ Remote.prototype.account_seq_cache = function (account, current, callback) { if (!request) { // console.log('starting: %s', account); - request = self.request_ledger_entry('account_root') - .account_root(account) - .ledger_choose(current) - .on('success', function (message) { - delete account_info.caching_seq_request; + request = self.request_ledger_entry('account_root'); + request.account_root(account); + request.ledger_choose(current); - var seq = message.node.Sequence; - account_info.seq = seq; + request.once('success', function (message) { + delete account_info.caching_seq_request; - // console.log('caching: %s %d', account, seq); - // If the caller also waits for 'success', they might run before this. - request.emit('success_account_seq_cache', message); - }) - .on('error', function (message) { - // console.log('error: %s', account); - delete account_info.caching_seq_request; + var seq = message.node.Sequence; + account_info.seq = seq; - request.emit('error_account_seq_cache', message); - }); + // console.log('caching: %s %d', account, seq); + // If the caller also waits for 'success', they might run before this. + request.emit('success_account_seq_cache', message); + }); + + request.once('error', function (message) { + // console.log('error: %s', account); + delete account_info.caching_seq_request; + + request.emit('error_account_seq_cache', message); + }); account_info.caching_seq_request = request; } @@ -1221,11 +1231,11 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account, request.message.source_currencies = src_currencies.map(function (ci) { var ci_new = {}; - if ('issuer' in ci) { + if (ci.hasOwnProperty('issuer')) { ci_new.issuer = UInt160.json_rewrite(ci.issuer); } - if ('currency' in ci) { + if (ci.hasOwnProperty('currency')) { ci_new.currency = Currency.json_rewrite(ci.currency); } @@ -1239,11 +1249,8 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account, }; Remote.prototype.request_path_find_close = function () { - var self = this; var request = new Request(this, 'path_find'); - - request.message.subcommand = 'close'; - + request.message.subcommand = 'close'; return request; }; @@ -1256,7 +1263,7 @@ Remote.prototype.request_unl_list = function (callback) { Remote.prototype.request_unl_add = function (addr, comment, callback) { var request = new Request(this, 'unl_add'); - request.message.node = addr; + request.message.node = addr; if (comment) { request.message.comment = note; From a99856bec08e5d8cfe0af214a1563dc7444ff978 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Tue, 6 Aug 2013 06:17:40 +0900 Subject: [PATCH 14/30] Cleanup --- src/js/ripple/account.js | 81 +++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/js/ripple/account.js b/src/js/ripple/account.js index b9425d61..a134d552 100644 --- a/src/js/ripple/account.js +++ b/src/js/ripple/account.js @@ -19,6 +19,7 @@ var Amount = require('./amount').Amount; var UInt160 = require('./uint160').UInt160; var TransactionManager = require('./transactionmanager').TransactionManager; + function Account(remote, account) { EventEmitter.call(this); @@ -34,45 +35,57 @@ function Account(remote, account) { // Important: This must never be overwritten, only extend()-ed this._entry = { }; - this.on('newListener', function (type, listener) { + function listener_added(type, listener) { if (~Account.subscribe_events.indexOf(type)) { if (!self._subs && self._remote._connected) { self._remote.request_subscribe() - .accounts(self._account_id) - .request(); + .accounts(self._account_id) + .request(); } self._subs += 1; } - }); + } - this.on('removeListener', function (type, listener) { + function listener_removed(type, listener) { if (~Account.subscribe_events.indexOf(type)) { self._subs -= 1; if (!self._subs && self._remote._connected) { self._remote.request_unsubscribe() - .accounts(self._account_id) - .request(); + .accounts(self._account_id) + .request(); } } - }); + } - this._remote.on('prepare_subscribe', function (request) { - if (self._subs) request.accounts(self._account_id); - }); + this.on('newListener', listener_added); + this.on('removeListener', listener_removed); - this.on('transaction', function (msg) { + function prepare_subscribe(request) { + if (self._subs) { + request.accounts(self._account_id); + } + } + + this._remote.on('prepare_subscribe', prepare_subscribe); + + function handle_transaction(transaction) { var changed = false; - msg.mmeta.each(function (an) { - if (an.entryType === 'AccountRoot' && - an.fields.Account === self._account_id) { + + transaction.mmeta.each(function(an) { + var isAccountRoot = an.entryType === 'AccountRoot' + && an.fields.Account === self._account_id; + if (isAccountRoot) { extend(self._entry, an.fieldsNew, an.fieldsFinal); changed = true; } }); + if (changed) { self.emit('entry', self._entry); } - }); + } + + this.on('transaction', handle_transaction); return this; }; @@ -97,6 +110,11 @@ Account.prototype.is_valid = function () { return this._account.is_valid(); }; +Account.prototype.get_account_info = function(callback) { + var callback = typeof callback === 'function' ? callback : function(){}; + this._remote.request_account_info(this._account_id, callback); +}; + /** * Retrieve the current AccountRoot entry. * @@ -107,32 +125,33 @@ Account.prototype.is_valid = function () { */ Account.prototype.entry = function (callback) { var self = this; - var callback = typeof callback === 'function' - ? callback - : function(){}; + var callback = typeof callback === 'function' ? callback : function(){}; - self._remote.request_account_info(this._account_id) - .on('success', function (e) { - extend(self._entry, e.account_data); + this.get_account_info(function account_info(err, info) { + if (err) { + callback(err); + } else { + extend(self._entry, info.account_data); self.emit('entry', self._entry); - callback(null, e); - }) - .on('error', function (e) { - callback(e); - }) - .request(); + callback(null, info); + } + }); return this; }; Account.prototype.get_next_sequence = function(callback) { - this._remote.request_account_info(this._account_id, function(err, entry) { + var callback = typeof callback === 'function' ? callback : function(){}; + + this.get_account_info(function account_info(err, info) { if (err) { callback(err); } else { - callback(null, entry.account_data.Sequence); + callback(null, info.account_data.Sequence); } }); + + return this; }; /** @@ -157,6 +176,6 @@ Account.prototype.submit = function(tx) { this._tx_manager.submit(tx); }; -exports.Account = Account; +exports.Account = Account; // vim:sw=2:sts=2:ts=8:et From 0fd973f58ba0552735c70d0d1197c8d56ad85170 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Tue, 6 Aug 2013 06:53:41 +0900 Subject: [PATCH 15/30] Add ledger_hash paramter to transaction_entry --- src/js/ripple/remote.js | 47 +++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index 2a55652b..e608292f 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -173,23 +173,26 @@ function Remote(opts, trace) { emitter.setMaxListeners(maxListeners); }); - this.on('newListener', function (type, listener) { + function listener_added(type, listener) { if (type === 'transaction_all') { if (!self._transaction_subs && self._connected) { self.request_subscribe('transactions').request(); } self._transaction_subs += 1; } - }); + } - this.on('removeListener', function (type, listener) { + function listener_removed(type, listener) { if (type === 'transaction_all') { self._transaction_subs -= 1; if (!self._transaction_subs && self._connected) { self.request_unsubscribe('transactions').request(); } } - }); + } + + this.on('newListener', listener_added); + this.on('removeListener', listener_removed); } util.inherits(Remote, EventEmitter); @@ -711,11 +714,22 @@ Remote.prototype.request_unsubscribe = function (streams, callback) { // .ledger_choose() // .ledger_hash() // .ledger_index() -Remote.prototype.request_transaction_entry = function (hash, callback) { +Remote.prototype.request_transaction = +Remote.prototype.request_transaction_entry = function (tx_hash, ledger_hash, callback) { //utils.assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. var request = new Request(this, 'transaction_entry'); - request.tx_hash(hash); + request.tx_hash(tx_hash); + + switch (typeof ledger_hash) { + case 'string': + request.ledger_hash(ledger_hash); + break; + case 'function': + callback = ledger_hash; + break; + } + request.callback(callback); return request; @@ -1047,10 +1061,7 @@ Remote.prototype.book = function (currency_gets, issuer_gets, currency_pays, iss var key = gets + ':' + pays; if (!this._books[key]) { - var book = new OrderBook( this, - currency_gets, issuer_gets, - currency_pays, issuer_pays - ); + var book = new OrderBook(this, currency_gets, issuer_gets, currency_pays, issuer_pays); if (!book.is_valid()) { return book; @@ -1098,7 +1109,9 @@ Remote.prototype.set_account_seq = function (account, seq) { Remote.prototype.account_seq_cache = function (account, current, callback) { var self = this; - if (!self.accounts[account]) self.accounts[account] = {}; + if (!self.accounts[account]) { + self.accounts[account] = {}; + } var account_info = self.accounts[account]; var request = account_info.caching_seq_request; @@ -1109,7 +1122,7 @@ Remote.prototype.account_seq_cache = function (account, current, callback) { request.account_root(account); request.ledger_choose(current); - request.once('success', function (message) { + function account_root_success(message) { delete account_info.caching_seq_request; var seq = message.node.Sequence; @@ -1118,14 +1131,17 @@ Remote.prototype.account_seq_cache = function (account, current, callback) { // console.log('caching: %s %d', account, seq); // If the caller also waits for 'success', they might run before this. request.emit('success_account_seq_cache', message); - }); + } - request.once('error', function (message) { + function account_root_error(message) { // console.log('error: %s', account); delete account_info.caching_seq_request; request.emit('error_account_seq_cache', message); - }); + } + + request.once('success', account_root_success); + request.once('error', account_root_error); account_info.caching_seq_request = request; } @@ -1138,7 +1154,6 @@ Remote.prototype.account_seq_cache = function (account, current, callback) { // Mark an account's root node as dirty. Remote.prototype.dirty_account_root = function (account) { var account = UInt160.json_rewrite(account); - delete this.ledgers.current.account_root[account]; }; From fd0ce52cf6649f91c8b1e9b5eac69c94c1550029 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Tue, 6 Aug 2013 06:54:06 +0900 Subject: [PATCH 16/30] Cleanup --- src/js/ripple/server.js | 106 ++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index 32f8a50b..425bbf4c 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -40,7 +40,7 @@ function Server(remote, opts) { this.on('response_subscribe', function(message) { self._handle_response_subscribe(message); }); -}; +} util.inherits(Server, EventEmitter); @@ -60,7 +60,7 @@ Server.online_states = [ Server.prototype._is_online = function (status) { return Server.online_states.indexOf(status) !== -1; -} +}; Server.prototype._set_state = function (state) { if (state !== this._state) { @@ -76,7 +76,7 @@ Server.prototype._set_state = function (state) { this.emit('disconnect'); } } -} +}; Server.prototype._remote_address = function() { var address = null; @@ -84,7 +84,7 @@ Server.prototype._remote_address = function() { address = this._ws._socket.remoteAddress; } return address; -} +}; Server.prototype.connect = function () { var self = this; @@ -93,12 +93,18 @@ Server.prototype.connect = function () { // recently received a message from the server and the WebSocket has not // reported any issues either. If we do fail to ping or the connection drops, // we will automatically reconnect. - if (this._connected === true) return; + if (this._connected) { + return; + } - if (this._remote.trace) console.log('server: connect: %s', this._opts.url); + if (this._remote.trace) { + console.log('server: connect: %s', this._opts.url); + } // Ensure any existing socket is given the command to close first. - if (this._ws) this._ws.close(); + if (this._ws) { + this._ws.close(); + } // We require this late, because websocket shims may be loaded after // ripple-lib. @@ -111,46 +117,48 @@ Server.prototype.connect = function () { ws.onopen = function () { // If we are no longer the active socket, simply ignore any event - if (ws !== self._ws) return; - - self.emit('socket_open'); - - // Subscribe to events - var request = self._remote._server_prepare_subscribe(); - self.request(request); + if (ws === self._ws) { + self.emit('socket_open'); + // Subscribe to events + var request = self._remote._server_prepare_subscribe(); + self.request(request); + } }; ws.onerror = function (e) { // If we are no longer the active socket, simply ignore any event - if (ws !== self._ws) return; + if (ws === self._ws) { + if (self._remote.trace) { + console.log('server: onerror: %s', e.data || e); + } - if (self._remote.trace) console.log('server: onerror: %s', e.data || e); + // Most connection errors for WebSockets are conveyed as 'close' events with + // code 1006. This is done for security purposes and therefore unlikely to + // ever change. - // Most connection errors for WebSockets are conveyed as 'close' events with - // code 1006. This is done for security purposes and therefore unlikely to - // ever change. + // This means that this handler is hardly ever called in practice. If it is, + // it probably means the server's WebSocket implementation is corrupt, or + // the connection is somehow producing corrupt data. - // This means that this handler is hardly ever called in practice. If it is, - // it probably means the server's WebSocket implementation is corrupt, or - // the connection is somehow producing corrupt data. + // Most WebSocket applications simply log and ignore this error. Once we + // support for multiple servers, we may consider doing something like + // lowering this server's quality score. - // Most WebSocket applications simply log and ignore this error. Once we - // support for multiple servers, we may consider doing something like - // lowering this server's quality score. - - // However, in Node.js this event may be triggered instead of the close - // event, so we need to handle it. - handleConnectionClose(); + // However, in Node.js this event may be triggered instead of the close + // event, so we need to handle it. + handleConnectionClose(); + } }; // Failure to open. ws.onclose = function () { // If we are no longer the active socket, simply ignore any event - if (ws !== self._ws) return; - - if (self._remote.trace) console.log('server: onclose: %s', ws.readyState); - - handleConnectionClose(); + if (ws === self._ws) { + if (self._remote.trace) { + console.log('server: onclose: %s', ws.readyState); + } + handleConnectionClose(); + } }; function handleConnectionClose() { @@ -161,14 +169,19 @@ Server.prototype.connect = function () { ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function () {}; // Should we be connected? - if (!self._should_connect) return; + if (!self._should_connect) { + return; + } // Delay and retry. self._retry += 1; self._retry_timer = setTimeout(function () { - if (self._remote.trace) console.log('server: retry'); - - if (!self._should_connect) return; + if (self._remote.trace) { + console.log('server: retry'); + } + if (!self._should_connect) { + return; + } self.connect(); }, self._retry < 40 ? 1000/20 // First, for 2 seconds: 20 times per second @@ -182,7 +195,7 @@ Server.prototype.connect = function () { ws.onmessage = function (msg) { self.emit('message', msg.data); }; -} +}; Server.prototype.disconnect = function () { this._should_connect = false; @@ -190,13 +203,13 @@ Server.prototype.disconnect = function () { if (this._ws) { this._ws.close(); } -} +}; Server.prototype.send_message = function (message) { if (this._ws) { this._ws.send(JSON.stringify(message)); } -} +}; /** * Submit a Request object to this server. @@ -229,19 +242,16 @@ Server.prototype.request = function (request) { self.send_message(request.message); }); } - } else { - if (this._remote.trace) { - utils.logObject('server: request: DROPPING: %s', request.message); - } + } else if (this._remote.trace) { + utils.logObject('server: request: DROPPING: %s', request.message); } }; Server.prototype._handle_message = function (json) { var self = this; + var message; - try { - var message = JSON.parse(json); - } catch(exception) { return; } + try { message = JSON.parse(json); } catch(exception) { } if (typeof message !== 'object' || typeof message.type === 'undefined') { return; From 1aaee7526b0c7d90b30b79a9808d65471bcf7cb9 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Wed, 7 Aug 2013 06:26:43 +0900 Subject: [PATCH 17/30] Cleanup --- src/js/ripple/remote.js | 227 +++++++++++++++++++--------------------- src/js/ripple/server.js | 91 ++++++++-------- 2 files changed, 152 insertions(+), 166 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index e608292f..c87d081b 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -42,6 +42,7 @@ var sjcl = require('../../../build/sjcl'); trace max_listeners : Set maxListeners for remote; prevents EventEmitter warnings + connection_offset : Connect to remote servers on supplied interval (in seconds) trusted : truthy, if remote is trusted max_fee : Maximum acceptable transaction fee fee_cushion : Extra fee multiplier to account for async fee changes. @@ -85,8 +86,8 @@ function Remote(opts, trace) { this.fee_cushion = (typeof opts.fee_cushion === 'undefined') ? 1.5 : Number(opts.fee_cushion); this.max_fee = (typeof opts.max_fee === 'undefined') ? Infinity : Number(opts.max_fee); this.id = 0; - this.trace = opts.trace || trace; - this._server_fatal = false; // True, if we know server exited. + this.trace = Boolean(opts.trace); + this._server_fatal = false; // True, if we know server exited. this._ledger_current_index = void(0); this._ledger_hash = void(0); this._ledger_time = void(0); @@ -94,8 +95,8 @@ function Remote(opts, trace) { this._testnet = void(0); this._transaction_subs = 0; this.online_target = false; - this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' - this.state = 'offline'; // 'online', 'offline' + this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' + this.state = 'offline'; // 'online', 'offline' this.retry_timer = void(0); this.retry = void(0); @@ -107,6 +108,7 @@ function Remote(opts, trace) { this._reserve_inc = void(0); this._connection_count = 0; this._connected = false; + this._connection_offset = 1000 * (Number(opts.connection_offset) || 5); this._last_tx = null; this._cur_path_find = null; @@ -207,12 +209,20 @@ Remote.flags = { } }; +function isTemMalformed(engine_result_code) { + return (engine_result_code >= -299 && engine_result_code < 199); +}; + +function isTefFailure(engine_result_code) { + return (engine_result_code >= -299 && engine_result_code < 199); +}; + Remote.from_config = function (obj, trace) { var serverConfig = typeof obj === 'string' ? config.servers[obj] : obj; var remote = new Remote(serverConfig, trace); - Object.keys(config.accounts).forEach(function(account) { + function initialize_account(account) { var accountInfo = config.accounts[account]; if (typeof accountInfo === 'object') { if (accountInfo.secret) { @@ -222,7 +232,13 @@ Remote.from_config = function (obj, trace) { remote.set_secret(accountInfo.account, accountInfo.secret); } } - }); + } + + if (typeof config.accounts === 'object') { + for (var account in config.accounts) { + initialize_account(account); + } + } return remote; }; @@ -233,14 +249,6 @@ Remote.create_remote = function(options, callback) { return remote; }; -var isTemMalformed = function (engine_result_code) { - return (engine_result_code >= -299 && engine_result_code < 199); -}; - -var isTefFailure = function (engine_result_code) { - return (engine_result_code >= -299 && engine_result_code < 199); -}; - Remote.prototype.add_server = function (opts) { var self = this; @@ -249,13 +257,13 @@ Remote.prototype.add_server = function (opts) { (opts.port || opts.websocket_port) ; - var server = new Server(this, {url: url}); + var server = new Server(this, { url: url }); - server.on('message', function (data) { - self._handle_message(data); - }); + function server_message(data) { + self._handle_message(data, server); + } - server.on('connect', function () { + function server_connect() { self._connection_count++; self._set_state('online'); if (opts.primary || !self._primary_server) { @@ -264,14 +272,18 @@ Remote.prototype.add_server = function (opts) { if (self._connection_count === self._servers.length) { self.emit('ready'); } - }); + } - server.on('disconnect', function () { + function server_disconnect() { self._connection_count--; if (!self._connection_count) { self._set_state('offline'); } - }); + } + + server.on('message', server_message); + server.on('connect', server_connect); + server.on('disconnect', server_disconnect); this._servers.push(server); @@ -285,9 +297,7 @@ Remote.prototype.server_fatal = function () { // Set the emitted state: 'online' or 'offline' Remote.prototype._set_state = function (state) { - if (this.trace) { - console.log('remote: set_state: %s', state); - } + this._trace('remote: set_state: %s', state); if (this.state !== state) { this.state = state; @@ -317,12 +327,18 @@ Remote.prototype.set_trace = function (trace) { return this; }; +Remote.prototype._trace = function() { + if (this.trace) { + utils.logObject.apply(utils, arguments); + } +}; + /** * Connect to the Ripple network. */ Remote.prototype.connect = function (online) { // Downwards compatibility - switch(typeof online) { + switch (typeof online) { case 'undefined': break; @@ -340,13 +356,18 @@ Remote.prototype.connect = function (online) { if (!this._servers.length) { throw new Error('No servers available.'); } else { - var servers = this._servers; + var self = this; + ;(function nextServer(i) { - var server = servers[i]; - server._sid = i; + var server = self._servers[i]; + + server._sid = ++i; server.connect(); - if (++i < servers.length) { - setTimeout(nextServer.bind(this, i), 1000 * 5); + + if (i < self._servers.length) { + setTimeout(function() { + nextServer(i); + }, self._connection_offset); } })(0); } @@ -358,31 +379,22 @@ Remote.prototype.connect = function (online) { * Disconnect from the Ripple network. */ Remote.prototype.disconnect = function (online) { - for (var i=0, l=this._servers.length; i Date: Wed, 7 Aug 2013 07:35:37 +0900 Subject: [PATCH 18/30] Add custom RippleError --- src/js/ripple/rippleerror.js | 22 ++++++++++++ src/js/ripple/transaction.js | 9 ++--- src/js/ripple/transactionmanager.js | 54 ++++++++++++++--------------- 3 files changed, 53 insertions(+), 32 deletions(-) create mode 100644 src/js/ripple/rippleerror.js diff --git a/src/js/ripple/rippleerror.js b/src/js/ripple/rippleerror.js new file mode 100644 index 00000000..68761b68 --- /dev/null +++ b/src/js/ripple/rippleerror.js @@ -0,0 +1,22 @@ +var util = require('util'); +var extend = require('extend'); + +function RippleError(code, message) { + if (typeof code === 'object') { + extend(this, code); + } else { + this.result = code; + this.result_message = message; + this.message = message; + } + + this.message = this.result_message || 'Error'; + + Error.captureStackTrace(this, code || this); +} + +util.inherits(RippleError, Error); + +RippleError.prototype.name = 'RippleError'; + +exports.RippleError = RippleError; diff --git a/src/js/ripple/transaction.js b/src/js/ripple/transaction.js index 26398a68..6bb43271 100644 --- a/src/js/ripple/transaction.js +++ b/src/js/ripple/transaction.js @@ -52,6 +52,7 @@ var Currency = require('./amount').Currency; var UInt160 = require('./amount').UInt160; var Seed = require('./seed').Seed; var SerializedObject = require('./serializedobject').SerializedObject; +var RippleError = require('./rippleerror').RippleError; var config = require('./config'); @@ -538,6 +539,9 @@ Transaction.prototype.submit = function (callback) { this.callback = typeof callback === 'function' ? callback : function(){}; function submission_error(error, message) { + if (!(error instanceof RippleError)) { + error = new RippleError(error); + } self.callback(error, message); } @@ -551,10 +555,7 @@ Transaction.prototype.submit = function (callback) { var account = this.tx_json.Account; if (typeof account !== 'string') { - this.emit('error', { - error: 'tejInvalidAccount', - error_message: 'Account is unspecified' - }); + this.emit('error', new RippleError('tejInvalidAccount', 'Account is unspecified')); } else { // YYY Might check paths for invalid accounts. this.remote.get_account(account).submit(this); diff --git a/src/js/ripple/transactionmanager.js b/src/js/ripple/transactionmanager.js index 77dec187..9ad1aaf5 100644 --- a/src/js/ripple/transactionmanager.js +++ b/src/js/ripple/transactionmanager.js @@ -1,5 +1,6 @@ -var EventEmitter = require('events').EventEmitter; -var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var util = require('util'); +var RippleError = require('./rippleerror').RippleError; /** * @constructor TransactionManager @@ -11,11 +12,12 @@ function TransactionManager(account) { var self = this; - this.account = account; - this.remote = account._remote; - this._timeout = void(0); - this._next_sequence = void(0); - this._config = { max_fee: self.remote.max_fee }; + this.account = account; + this.remote = account._remote; + this._timeout = void(0); + this._resubmitting = false; + this._next_sequence = void(0); + this._config = { max_fee: self.remote.max_fee }; function sequence_loaded(err, sequence) { self._next_sequence = sequence; @@ -23,7 +25,7 @@ function TransactionManager(account) { } account.get_next_sequence(sequence_loaded); -} +}; util.inherits(TransactionManager, EventEmitter); @@ -31,6 +33,7 @@ util.inherits(TransactionManager, EventEmitter); * @param {Object} tx */ +//called whenever we get a transaction for an account whose transactions we're managing. //TransactionManager.prototype.update = function(tx) { // this._next_sequence_number = Math.max(tx.meta.account_next_seq, this._next_sequence_number); // var queued; @@ -97,18 +100,12 @@ TransactionManager.prototype._request = function(tx) { var remote = this.remote; if (!tx._secret && !tx.tx_json.TxnSignature) { - tx.emit('error', { - result: 'tejSecretUnknown', - result_message: 'Could not sign transactions because we.' - }); + tx.emit('error', new RippleError('tejSecretUnknown', 'Missing secret')); return; } if (!remote.trusted && !remote.local_signing) { - tx.emit('error', { - result: 'tejServerUntrusted', - result_message: 'Attempt to give a secret to an untrusted server.' - }); + tx.emit('error', new RippleError('tejServerUntrusted', 'Attempt to give secret to untrusted server')); return; } @@ -139,9 +136,10 @@ TransactionManager.prototype._request = function(tx) { submit_request.tx_json(tx.tx_json); } + // Handle insufficient fee function submission_success(message) { if (!message.engine_result) { - return submission_error(message); + submission_error(message); } else { tx.hash = message.tx_json.hash; tx.set_state('client_proposed'); @@ -165,15 +163,17 @@ TransactionManager.prototype._request = function(tx) { function submission_error(err) { tx.set_state('remoteError'); - tx.emit('error', err); + tx.emit('error', new RippleError(err)); } submit_request.once('success', submission_success); submit_request.once('error', submission_error); - //submit_request.timeout(1000 * 10, function() { - //tx.emit('error', new Error('Timeout')); - //}); +// submit_request.timeout(1000 * 2, function() { +// tx.emit('error', new Error('Timeout')); +// }); + + //submit_request.emit = function() {} submit_request.request(); @@ -181,7 +181,7 @@ TransactionManager.prototype._request = function(tx) { tx.emit('submitted'); return submit_request; -} +}; TransactionManager.prototype._is_not_found = function(error) { return !!error && typeof error === 'object' @@ -190,7 +190,7 @@ TransactionManager.prototype._is_not_found = function(error) { && (error.remote.error === 'transactionNotFound' || error.remote.error === 'ledgerNotFound') ; -} +}; TransactionManager.prototype._detect_ledger_entry = function(tx) { var self = this; @@ -215,9 +215,7 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) { checked_ledgers[ledger_hash] = true; - var request_transaction = remote.request_transaction_entry(tx.hash); - - request_transaction.ledger_hash(ledger_hash); + var request_transaction = remote.request_transaction_entry(tx.hash, ledger_hash); request_transaction.callback(function(err, message) { if (tx.finalized) return; @@ -259,7 +257,7 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) { tx.once('proposed', transaction_proposed); tx.once('final', transaction_finalized); -} +}; /** * @param {Object} tx @@ -282,6 +280,6 @@ TransactionManager.prototype.submit = function(tx) { this._request(tx); } -} +}; exports.TransactionManager = TransactionManager; From 67d100796da082bcb3be46f7144d18e807b4d430 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Wed, 7 Aug 2013 08:16:20 +0900 Subject: [PATCH 19/30] Cleanup --- src/js/ripple/remote.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index c87d081b..4141d23f 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -27,6 +27,7 @@ var Transaction = require('./transaction').Transaction; var Account = require('./account').Account; var Meta = require('./meta').Meta; var OrderBook = require('./orderbook').OrderBook; +var PathFind = require('./pathfind').PathFind; var utils = require('./utils'); var config = require('./config'); @@ -165,8 +166,8 @@ function Remote(opts, trace) { } opts.servers.forEach(function(server) { - var i = Number(server.pool) || 1; - while (i--) { self.add_server(server); } + var pool = Number(server.pool) || 1; + while (pool--) { self.add_server(server); }; }); // This is used to remove Node EventEmitter warnings @@ -360,9 +361,8 @@ Remote.prototype.connect = function (online) { ;(function nextServer(i) { var server = self._servers[i]; - - server._sid = ++i; server.connect(); + server._sid = ++i; if (i < self._servers.length) { setTimeout(function() { From 48a2bb9fe4a2f674a8781ed212879da72da9e019 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Wed, 7 Aug 2013 08:37:07 +0900 Subject: [PATCH 20/30] Update readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9efbca86..c852fb8c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ remote.request_server_info(function(err, res) { **request_unsubscribe(streams, [callback])** -**request_transaction_entry(hash, [callback])** +**request_transaction_entry(tx_hash, [ledger_hash], [callback])** **request_tx(hash, [callback])** @@ -90,11 +90,11 @@ remote.request_server_info(function(err, res) { **request_wallet_accounts(seed, [callback])** -+ requires trusted **remote ++ requires trusted remote **request_sign(secret, tx_json, [callback])** -+ requires trusted **remote ++ requires trusted remote **request_submit([callback])** @@ -118,6 +118,6 @@ remote.request_server_info(function(err, res) { **request_connect(ip, port, [callback])** -**transaction()** +**transaction([destination], [source], [amount], [callback])** + returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object From e5421effd5d952ea37529b0d8870c11316280398 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Wed, 7 Aug 2013 17:11:29 -0700 Subject: [PATCH 21/30] Allow hooking of the Server WebSocket constructor object --- src/js/ripple/index.js | 1 + src/js/ripple/server.js | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/js/ripple/index.js b/src/js/ripple/index.js index 1cbf2d1e..692155f3 100644 --- a/src/js/ripple/index.js +++ b/src/js/ripple/index.js @@ -10,6 +10,7 @@ exports.SerializedObject = require('./serializedobject').SerializedObject; exports.binformat = require('./binformat'); exports.utils = require('./utils'); +exports.Server = require('./server').Server; // Important: We do not guarantee any specific version of SJCL or for any // specific features to be included. The version and configuration may change at diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index ec68c704..a31d0f80 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -89,6 +89,16 @@ Server.prototype._remote_address = function() { return address; }; +// This is the final interface between client code and a socket connection to a +// `rippled` server. As such, this is a decent hook point to allow a WebSocket +// interface conforming object to be used as a basis to mock rippled. This +// avoids the need to bind a websocket server to a port and allows a more +// synchronous style of code to represent a client <-> server message sequence. +// We can also use this to log a message sequence to a buffer. +Server.prototype.websocketConstructor = function () { + return require('ws'); +}; + Server.prototype.connect = function () { var self = this; @@ -109,7 +119,7 @@ Server.prototype.connect = function () { // We require this late, because websocket shims may be loaded after // ripple-lib. - var WebSocket = require('ws'); + var WebSocket = this.websocketConstructor(); var ws = this._ws = new WebSocket(this._opts.url); this._should_connect = true; From e8144128d083b01615f1da05f8588e77eb71f500 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Wed, 7 Aug 2013 17:20:23 -0700 Subject: [PATCH 22/30] Naming style, oopz --- src/js/ripple/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index a31d0f80..1d7127bc 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -95,7 +95,7 @@ Server.prototype._remote_address = function() { // avoids the need to bind a websocket server to a port and allows a more // synchronous style of code to represent a client <-> server message sequence. // We can also use this to log a message sequence to a buffer. -Server.prototype.websocketConstructor = function () { +Server.prototype.websocket_constructor = function () { return require('ws'); }; @@ -119,7 +119,7 @@ Server.prototype.connect = function () { // We require this late, because websocket shims may be loaded after // ripple-lib. - var WebSocket = this.websocketConstructor(); + var WebSocket = this.websocket_constructor(); var ws = this._ws = new WebSocket(this._opts.url); this._should_connect = true; From 08a2d771cd2c0de4250b234e27b972c188b1f5d5 Mon Sep 17 00:00:00 2001 From: Vahe Hovhannisyan Date: Thu, 8 Aug 2013 11:26:20 +0200 Subject: [PATCH 23/30] WebSocket support for some old browsers --- web_modules/ws.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web_modules/ws.js b/web_modules/ws.js index b2fca770..f384ff5d 100644 --- a/web_modules/ws.js +++ b/web_modules/ws.js @@ -1 +1,6 @@ -module.exports = WebSocket; +// If there is no WebSocket, try MozWebSocket (support for some old browsers) +try { + module.exports = WebSocket +} catch(err) { + module.exports = MozWebSocket +} \ No newline at end of file From 7c5e9bae2a11b180054a8a93297acd29100a248a Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Thu, 8 Aug 2013 18:43:06 -0700 Subject: [PATCH 24/30] Use the initial path find response as a first set of alternatives. --- src/js/ripple/pathfind.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/js/ripple/pathfind.js b/src/js/ripple/pathfind.js index 6dba8579..6dd1835c 100644 --- a/src/js/ripple/pathfind.js +++ b/src/js/ripple/pathfind.js @@ -38,10 +38,21 @@ util.inherits(PathFind, EventEmitter); */ PathFind.prototype.create = function () { + var self = this; + var req = this.remote.request_path_find_create(this.src_account, this.dst_account, this.dst_amount, - this.src_currencies); + this.src_currencies, + handleInitialPath); + + function handleInitialPath(err, msg) { + if (err) { + // XXX Handle error + return; + } + self.notify_update(msg); + } req.request(); }; From e49838437ee9f6b08fab1f79be0b011419ecfd4a Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Thu, 8 Aug 2013 18:43:16 -0700 Subject: [PATCH 25/30] Add Account#lines. --- src/js/ripple/account.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/js/ripple/account.js b/src/js/ripple/account.js index 603ad7d1..6dbbd436 100644 --- a/src/js/ripple/account.js +++ b/src/js/ripple/account.js @@ -127,6 +127,35 @@ Account.prototype.entry = function (callback) return this; }; +/** + * Retrieve this account's Ripple trust lines. + * + * To keep up-to-date with changes to the AccountRoot entry, subscribe to the + * "lines" event. (Not yet implemented.) + * + * @param {function (err, lines)} callback Called with the result + */ +Account.prototype.lines = function (callback) +{ + var self = this; + + self._remote.request_account_lines(this._account_id) + .on('success', function (e) { + self._lines = e.lines; + self.emit('lines', self._lines); + + if ("function" === typeof callback) { + callback(null, e); + } + }) + .on('error', function (e) { + callback(e); + }) + .request(); + + return this; +}; + /** * Notify object of a relevant transaction. * From 8640176a078a7142c16d4dd4028611d788c67800 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 9 Aug 2013 17:09:30 -0700 Subject: [PATCH 26/30] Move debug statement in pathfind.js to proper place. --- src/js/ripple/pathfind.js | 1 - src/js/ripple/server.js | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/ripple/pathfind.js b/src/js/ripple/pathfind.js index 6dd1835c..3c456140 100644 --- a/src/js/ripple/pathfind.js +++ b/src/js/ripple/pathfind.js @@ -72,7 +72,6 @@ PathFind.prototype.notify_update = function (message) // Only pass the event along if this path find response matches what we were // looking for. - if (message.alternatives && message.alternatives.length) console.log(Amount.from_json(message.alternatives[0].source_amount).to_text_full()); if (this.src_account === src_account && this.dst_account === dst_account && this.dst_amount.equals(dst_amount)) { diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index a9275f5d..55eea013 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -262,6 +262,10 @@ Server.prototype._handle_message = function (json) { } break; + case 'path_find': + if (self._remote.trace) utils.logObject('server: path_find: %s', message); + break; + case 'serverStatus': // This message is only received when online. As we are connected, it is the definative final state. self._set_state(self._is_online(message.server_status) ? 'online' : 'offline'); From 1e1e70d9a6dbe00583a4a8f538d4786d30309482 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 14 Aug 2013 17:11:30 -0700 Subject: [PATCH 27/30] Add Amount#invert. --- src/js/ripple/amount.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/js/ripple/amount.js b/src/js/ripple/amount.js index 2f3c671e..441bde90 100644 --- a/src/js/ripple/amount.js +++ b/src/js/ripple/amount.js @@ -556,6 +556,21 @@ Amount.prototype.negate = function () { return this.clone('NEGATE'); }; +/** + * Invert this amount and return the new value. + * + * Creates a new Amount object as a copy of the current one (including the same + * unit (currency & issuer), inverts it (1/x) and returns the result. + */ +Amount.prototype.invert = function () { + var one = this.clone(); + one._value = BigInteger.ONE; + one._offset = 0; + one._is_negative = false; + one.canonicalize(); + return one.ratio_human(this); +}; + /** * Tries to correctly interpret an amount as entered by a user. * From 1305acb84904851d454623fcff6129d2a138c46d Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 14 Aug 2013 17:15:14 -0700 Subject: [PATCH 28/30] Bump version to 0.7.19. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 578dd90f..cb79f584 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ripple-lib", - "version": "0.7.18", + "version": "0.7.19", "description": "Ripple JavaScript client library", "files": [ "src/js/ripple/*.js", From b3822cc4c5f55e3f648aa91422aaf28233923677 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Wed, 14 Aug 2013 17:20:51 -0700 Subject: [PATCH 29/30] Update everything --- src/js/ripple/account.js | 6 +- src/js/ripple/remote.js | 224 +++++++++-------- src/js/ripple/request.js | 118 +++++---- src/js/ripple/rippleerror.js | 4 +- src/js/ripple/transaction.js | 122 ++++------ src/js/ripple/transactionmanager.js | 362 +++++++++++++++++----------- src/js/ripple/transactionqueue.js | 59 +++++ 7 files changed, 507 insertions(+), 388 deletions(-) create mode 100644 src/js/ripple/transactionqueue.js diff --git a/src/js/ripple/account.js b/src/js/ripple/account.js index 2f8ed878..09b15574 100644 --- a/src/js/ripple/account.js +++ b/src/js/ripple/account.js @@ -110,7 +110,7 @@ Account.prototype.is_valid = function () { return this._account.is_valid(); }; -Account.prototype.get_account_info = function(callback) { +Account.prototype.get_info = function(callback) { var callback = typeof callback === 'function' ? callback : function(){}; this._remote.request_account_info(this._account_id, callback); }; @@ -127,7 +127,7 @@ Account.prototype.entry = function (callback) { var self = this; var callback = typeof callback === 'function' ? callback : function(){}; - this.get_account_info(function account_info(err, info) { + this.get_info(function account_info(err, info) { if (err) { callback(err); } else { @@ -143,7 +143,7 @@ Account.prototype.entry = function (callback) { Account.prototype.get_next_sequence = function(callback) { var callback = typeof callback === 'function' ? callback : function(){}; - this.get_account_info(function account_info(err, info) { + this.get_info(function account_info(err, info) { if (err) { callback(err); } else { diff --git a/src/js/ripple/remote.js b/src/js/ripple/remote.js index 4141d23f..403d41e0 100644 --- a/src/js/ripple/remote.js +++ b/src/js/ripple/remote.js @@ -28,6 +28,7 @@ var Account = require('./account').Account; var Meta = require('./meta').Meta; var OrderBook = require('./orderbook').OrderBook; var PathFind = require('./pathfind').PathFind; +var RippleError = require('./rippleerror').RippleError; var utils = require('./utils'); var config = require('./config'); @@ -253,12 +254,11 @@ Remote.create_remote = function(options, callback) { Remote.prototype.add_server = function (opts) { var self = this; - var url = ((opts.secure || opts.websocket_ssl) ? 'wss://' : 'ws://') + - (opts.host || opts.websocket_ip) + ':' + - (opts.port || opts.websocket_port) - ; - - var server = new Server(this, { url: url }); + var server = new Server(this, { + host : opts.host || opts.websocket_ip, + port : opts.port || opts.websocket_port, + secure : opts.secure || opts.websocket_ssl + }); function server_message(data) { self._handle_message(data, server); @@ -338,39 +338,36 @@ Remote.prototype._trace = function() { * Connect to the Ripple network. */ Remote.prototype.connect = function (online) { - // Downwards compatibility + if (!this._servers.length) { + throw new Error('No servers available.'); + } + switch (typeof online) { case 'undefined': break; - case 'function': this.once('connect', online); break; - default: + // Downwards compatibility if (!Boolean(online)) { return this.disconnect(); } - break; } - if (!this._servers.length) { - throw new Error('No servers available.'); - } else { - var self = this; + var self = this; - ;(function nextServer(i) { - var server = self._servers[i]; - server.connect(); - server._sid = ++i; + ;(function nextServer(i) { + var server = self._servers[i]; + server.connect(); + server._sid = ++i; - if (i < self._servers.length) { - setTimeout(function() { - nextServer(i); - }, self._connection_offset); - } - })(0); - } + if (i < self._servers.length) { + setTimeout(function() { + nextServer(i); + }, self._connection_offset); + } + })(0); return this; }; @@ -379,6 +376,10 @@ Remote.prototype.connect = function (online) { * Disconnect from the Ripple network. */ Remote.prototype.disconnect = function (online) { + if (!this._servers.length) { + throw new Error('No servers available, not disconnecting'); + } + this._servers.forEach(function(server) { server.disconnect(); }); @@ -398,10 +399,7 @@ Remote.prototype._handle_message = function (message, server) { if (unexpected) { // Unexpected response from remote. - this.emit('error', { - error: 'remoteUnexpected', - error_message: 'Unexpected response from remote.' - }); + this.emit('error', new RippleError('remoteUnexpected', 'Unexpected response from remote')); return; } @@ -461,34 +459,34 @@ Remote.prototype._handle_message = function (message, server) { this.emit('transaction_all', message); break; - case 'path_find': - // Pass the event to the currently open PathFind object - if (this._cur_path_find) { - this._cur_path_find.notify_update(message); - } + case 'path_find': + // Pass the event to the currently open PathFind object + if (this._cur_path_find) { + this._cur_path_find.notify_update(message); + } - this.emit('path_find_all', message); - break; - case 'serverStatus': - self.emit('server_status', message); + this.emit('path_find_all', message); + break; + case 'serverStatus': + self.emit('server_status', message); - var load_changed = message.hasOwnProperty('load_base') - && message.hasOwnProperty('load_factor') - && (message.load_base !== self._load_base || message.load_factor !== self._load_factor) - ; + var load_changed = message.hasOwnProperty('load_base') + && message.hasOwnProperty('load_factor') + && (message.load_base !== self._load_base || message.load_factor !== self._load_factor) + ; - if (load_changed) { - self._load_base = message.load_base; - self._load_factor = message.load_factor; - self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); - } - break; + if (load_changed) { + self._load_base = message.load_base; + self._load_factor = message.load_factor; + self.emit('load', { 'load_base' : self._load_base, 'load_factor' : self.load_factor }); + } + break; // All other messages - default: - this._trace('remote: '+message.type+': %s', message); - this.emit('net_' + message.type, message); - break; + default: + this._trace('remote: ' + message.type + ': %s', message); + this.emit('net_' + message.type, message); + break; } }; @@ -544,8 +542,10 @@ Remote.prototype.request = function (request) { request.emit('error', new Error('No servers available')); } else if (!this._connected) { this.once('connect', this.request.bind(this, request)); + } else if (request.server === null) { + this.emit('error', new Error('Server does not exist')); } else { - var server = this._get_server(); + var server = request.server || this._get_server(); if (server) { server.request(request); } else { @@ -739,9 +739,9 @@ Remote.prototype.request_transaction_entry = function (tx_hash, ledger_hash, cal case 'string': request.ledger_hash(ledger_hash); break; - case 'function': + default: + request.ledger_index('validated'); callback = ledger_hash; - break; } request.callback(callback); @@ -752,20 +752,16 @@ Remote.prototype.request_transaction_entry = function (tx_hash, ledger_hash, cal // DEPRECATED: use request_transaction_entry Remote.prototype.request_tx = function (hash, callback) { var request = new Request(this, 'tx'); - request.message.transaction = hash; request.callback(callback); - return request; }; Remote.prototype.request_account_info = function (accountID, callback) { var request = new Request(this, 'account_info'); - request.message.ident = UInt160.json_rewrite(accountID); // DEPRECATED request.message.account = UInt160.json_rewrite(accountID); request.callback(callback); - return request; }; @@ -875,32 +871,23 @@ Remote.prototype.request_book_offers = function (gets, pays, taker, callback) { Remote.prototype.request_wallet_accounts = function (seed, callback) { utils.assert(this.trusted); // Don't send secrets. - var request = new Request(this, 'wallet_accounts'); - request.message.seed = seed; - return request.callback(callback); }; Remote.prototype.request_sign = function (secret, tx_json, callback) { utils.assert(this.trusted); // Don't send secrets. - var request = new Request(this, 'sign'); - request.message.secret = secret; request.message.tx_json = tx_json; - request.callback(callback); - return request; }; // Submit a transaction. Remote.prototype.request_submit = function (callback) { - var request = new Request(this, 'submit'); - request.callback(callback); - return request; + return new Request(this, 'submit').callback(callback); }; // @@ -926,7 +913,7 @@ Remote.prototype._server_prepare_subscribe = function (callback) { var request = this.request_subscribe(feeds); - request.on('success', function (message) { + request.once('success', function (message) { self._stand_alone = !!message.stand_alone; self._testnet = !!message.testnet; @@ -949,7 +936,7 @@ Remote.prototype._server_prepare_subscribe = function (callback) { // XXX When we have multiple server support, most of this should be tracked // by the Server objects and then aggregated/interpreted by Remote. self._load_base = message.load_base || 256; - self._load_factor = message.load_factor || 1.0; + self._load_factor = message.load_factor || 256; self._fee_ref = message.fee_ref; self._fee_base = message.fee_base; self._reserve_base = message.reserve_base; @@ -979,52 +966,42 @@ Remote.prototype.ledger_accept = function (callback) { } else { this.emit('error', { error : 'notStandAlone' }); } - return this; }; // Return a request to refresh the account balance. Remote.prototype.request_account_balance = function (account, current, callback) { var request = this.request_ledger_entry('account_root'); - request.account_root(account); request.ledger_choose(current); request.once('success', function (message) { request.emit('account_balance', Amount.from_json(message.node.Balance)); }); - request.callback(callback, 'account_balance'); - return request; }; // Return a request to return the account flags. Remote.prototype.request_account_flags = function (account, current, callback) { var request = this.request_ledger_entry('account_root'); - request.account_root(account); request.ledger_choose(current); request.on('success', function (message) { request.emit('account_flags', message.node.Flags); }); - request.callback(callback, 'account_flags'); - return request; }; // Return a request to emit the owner count. Remote.prototype.request_owner_count = function (account, current, callback) { var request = this.request_ledger_entry('account_root'); - request.account_root(account); request.ledger_choose(current); request.on('success', function (message) { request.emit('owner_count', message.node.OwnerCount); }); - request.callback(callback, 'owner_count'); - return request; }; @@ -1082,7 +1059,7 @@ Remote.prototype.account_seq = function (account, advance) { if (account_info && account_info.seq) { seq = account_info.seq; - account_info.seq += { ADVANCE: 1, REWIND: -1 }[advance] || 0; + account_info.seq += { ADVANCE: 1, REWIND: -1 }[advance.toUpperCase()] || 0; } return seq; @@ -1103,7 +1080,7 @@ Remote.prototype.account_seq_cache = function (account, current, callback) { var self = this; if (!this.accounts.hasOwnProperty(account)) { - self.accounts[account] = { }; + this.accounts[account] = { }; } var account_info = this.accounts[account]; @@ -1198,6 +1175,20 @@ Remote.prototype.request_ripple_balance = function (account, issuer, currency, c return request; }; +function prepare_currencies(ci) { + var ci_new = { }; + + if (ci.hasOwnProperty('issuer')) { + ci_new.issuer = UInt160.json_rewrite(ci.issuer); + } + + if (ci.hasOwnProperty('currency')) { + ci_new.currency = Currency.json_rewrite(ci.currency); + } + + return ci_new; +} + Remote.prototype.request_ripple_path_find = function (src_account, dst_account, dst_amount, src_currencies, callback) { var request = new Request(this, 'ripple_path_find'); @@ -1206,19 +1197,7 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, request.message.destination_amount = Amount.json_rewrite(dst_amount); if (src_currencies) { - request.message.source_currencies = src_currencies.map(function (ci) { - var ci_new = { }; - - if (ci.hasOwnProperty('issuer')) { - ci_new.issuer = UInt160.json_rewrite(ci.issuer); - } - - if (ci.hasOwnProperty('currency')) { - ci_new.currency = Currency.json_rewrite(ci.currency); - } - - return ci_new; - }); + request.message.source_currencies = src_currencies.map(prepare_currencies); } request.callback(callback); @@ -1227,7 +1206,6 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, }; Remote.prototype.request_path_find_create = function (src_account, dst_account, dst_amount, src_currencies, callback) { - var self = this; var request = new Request(this, 'path_find'); request.message.subcommand = 'create'; @@ -1236,19 +1214,7 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account, request.message.destination_amount = Amount.json_rewrite(dst_amount); if (src_currencies) { - request.message.source_currencies = src_currencies.map(function (ci) { - var ci_new = {}; - - if (ci.hasOwnProperty('issuer')) { - ci_new.issuer = UInt160.json_rewrite(ci.issuer); - } - - if (ci.hasOwnProperty('currency')) { - ci_new.currency = Currency.json_rewrite(ci.currency); - } - - return ci_new; - }); + request.message.source_currencies = src_currencies.map(prepare_currencies); } request.callback(callback); @@ -1263,9 +1229,7 @@ Remote.prototype.request_path_find_close = function () { }; Remote.prototype.request_unl_list = function (callback) { - var request = new Request(this, 'unl_list'); - request.callback(callback); - return request; + return new Request(this, 'unl_list').callback(callback); }; Remote.prototype.request_unl_add = function (addr, comment, callback) { @@ -1291,9 +1255,7 @@ Remote.prototype.request_unl_delete = function (node, callback) { }; Remote.prototype.request_peers = function (callback) { - var request = new Request(this, 'peers'); - request.callback(callback); - return request; + return new Request(this, 'peers').callback(callback); }; Remote.prototype.request_connect = function (ip, port, callback) { @@ -1372,6 +1334,36 @@ Remote.prototype.reserve = function (owner_count) { return reserve_base.add(reserve_inc.product_human(owner_count)); }; +Remote.prototype.ping = function(host, callback) { + var request = new Request(this, 'ping'); + + switch (typeof host) { + case 'function': + callback = host; + break; + case 'string': + var server = null; + for (var i=0, s; s=this._servers[i]; i++) { + if (s._host === host) { + server = s; + break; + } + } + request.set_server(server); + break; + } + + var then = Date.now(); + + request.once('success', function() { + request.emit('pong', Date.now() - then); + }); + + request.callback(callback, 'pong'); + + return request; +}; + exports.Remote = Remote; // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/ripple/request.js b/src/js/ripple/request.js index 651b89e7..960c927e 100644 --- a/src/js/ripple/request.js +++ b/src/js/ripple/request.js @@ -6,6 +6,7 @@ var Transaction = require('./transaction').Transaction; var Account = require('./account').Account; var Meta = require('./meta').Meta; var OrderBook = require('./orderbook').OrderBook; +var RippleError = require('./rippleerror').RippleError; // Request events emitted: // 'success' : Request successful. @@ -15,6 +16,7 @@ var OrderBook = require('./orderbook').OrderBook; // 'remoteDisconnected' function Request(remote, command) { EventEmitter.call(this); + this.remote = remote; this.requested = false; this.message = { @@ -28,7 +30,7 @@ util.inherits(Request, EventEmitter); // Send the request to a remote. Request.prototype.request = function (remote) { if (!this.requested) { - this.requested = true; + this.requested = true; this.remote.request(this); this.emit('request', remote); } @@ -36,8 +38,19 @@ Request.prototype.request = function (remote) { Request.prototype.callback = function(callback, successEvent, errorEvent) { if (callback && typeof callback === 'function') { - this.once(successEvent || 'success', callback.bind(this, null)); - this.once(errorEvent || 'error' , callback.bind(this)); + function request_success(message) { + callback.call(this, null, message); + } + + function request_error(error) { + if (!(error instanceof RippleError)) { + error = new RippleError(error); + } + callback.call(this, error); + } + + this.once(successEvent || 'success', request_success); + this.once(errorEvent || 'error' , request_error); this.request(); } @@ -45,12 +58,16 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) { }; Request.prototype.timeout = function(duration, callback) { - if (!this.requested) { - this.once('request', this.timeout.bind(this, duration, callback)); - return; - }; + var self = this; + + if (!this.requested) { + function requested() { + self.timeout(duration, callback); + } + this.once('request', requested); + return; + } - var self = this; var emit = this.emit; var timed_out = false; @@ -61,19 +78,23 @@ Request.prototype.timeout = function(duration, callback) { }, duration); this.emit = function() { - if (timed_out) return; - else clearTimeout(timeout); - emit.apply(self, arguments); + if (!timed_out) { + clearTimeout(timeout); + emit.apply(self, arguments); + } }; return this; }; +Request.prototype.set_server = function(server) { + this.server = server; +}; + Request.prototype.build_path = function (build) { if (build) { this.message.build_path = true; } - return this; }; @@ -83,16 +104,14 @@ Request.prototype.ledger_choose = function (current) { } else { this.message.ledger_hash = this.remote._ledger_hash; } - return this; }; // Set the ledger for a request. // - ledger_entry // - transaction_entry -Request.prototype.ledger_hash = function (h) { - this.message.ledger_hash = h; - +Request.prototype.ledger_hash = function (hash) { + this.message.ledger_hash = hash; return this; }; @@ -100,7 +119,6 @@ Request.prototype.ledger_hash = function (h) { // - ledger_entry Request.prototype.ledger_index = function (ledger_index) { this.message.ledger_index = ledger_index; - return this; }; @@ -114,10 +132,10 @@ Request.prototype.ledger_select = function (ledger_spec) { default: // XXX Better test needed - if (String(ledger_spec).length > 12) { - this.message.ledger_hash = ledger_spec; + if (Number(ledger_spec)) { + this.message.ledger_index = ledger_spec; } else { - this.message.ledger_index = ledger_spec; + this.message.ledger_hash = ledger_spec; } break; } @@ -127,13 +145,11 @@ Request.prototype.ledger_select = function (ledger_spec) { Request.prototype.account_root = function (account) { this.message.account_root = UInt160.json_rewrite(account); - return this; }; Request.prototype.index = function (hash) { this.message.index = hash; - return this; }; @@ -145,52 +161,45 @@ Request.prototype.offer_id = function (account, seq) { account: UInt160.json_rewrite(account), seq: seq }; - return this; }; // --> index : ledger entry index. Request.prototype.offer_index = function (index) { this.message.offer = index; - return this; }; -Request.prototype.secret = function (s) { - if (s) { - this.message.secret = s; +Request.prototype.secret = function (secret) { + if (secret) { + this.message.secret = secret; } - return this; }; -Request.prototype.tx_hash = function (h) { - this.message.tx_hash = h; - +Request.prototype.tx_hash = function (hash) { + this.message.tx_hash = hash; return this; }; -Request.prototype.tx_json = function (j) { - this.message.tx_json = j; - +Request.prototype.tx_json = function (json) { + this.message.tx_json = json; return this; }; -Request.prototype.tx_blob = function (j) { - this.message.tx_blob = j; - +Request.prototype.tx_blob = function (json) { + this.message.tx_blob = json; return this; }; Request.prototype.ripple_state = function (account, issuer, currency) { this.message.ripple_state = { - 'accounts' : [ + currency : currency, + accounts : [ UInt160.json_rewrite(account), UInt160.json_rewrite(issuer) - ], - 'currency' : currency + ] }; - return this; }; @@ -200,14 +209,14 @@ Request.prototype.accounts = function (accounts, realtime) { } // Process accounts parameters - var procAccounts = accounts.map(function(account) { + var processedAccounts = accounts.map(function(account) { return UInt160.json_rewrite(account); }); if (realtime) { - this.message.rt_accounts = procAccounts; + this.message.rt_accounts = processedAccounts; } else { - this.message.accounts = procAccounts; + this.message.accounts = processedAccounts; } return this; @@ -218,14 +227,16 @@ Request.prototype.rt_accounts = function (accounts) { }; Request.prototype.books = function (books, snapshot) { - var procBooks = []; + var processedBooks = [ ]; for (var i = 0, l = books.length; i < l; i++) { var book = books[i]; - var json = {}; + var json = { }; function processSide(side) { - if (!book[side]) throw new Error('Missing '+side); + if (!book[side]) { + throw new Error('Missing ' + side); + } var obj = json[side] = { currency: Currency.json_rewrite(book[side].currency) @@ -239,13 +250,18 @@ Request.prototype.books = function (books, snapshot) { processSide('taker_gets'); processSide('taker_pays'); - if (snapshot) json.snapshot = true; - if (book.both) json.both = true; + if (snapshot) { + json.snapshot = true; + } - procBooks.push(json); + if (book.both) { + json.both = true; + } + + processedBooks.push(json); } - this.message.books = procBooks; + this.message.books = processedBooks; return this; }; diff --git a/src/js/ripple/rippleerror.js b/src/js/ripple/rippleerror.js index 68761b68..a1f9a4e6 100644 --- a/src/js/ripple/rippleerror.js +++ b/src/js/ripple/rippleerror.js @@ -5,9 +5,9 @@ function RippleError(code, message) { if (typeof code === 'object') { extend(this, code); } else { - this.result = code; + this.result = code; + this.message = message; this.result_message = message; - this.message = message; } this.message = this.result_message || 'Error'; diff --git a/src/js/ripple/transaction.js b/src/js/ripple/transaction.js index 6bb43271..5e6fee95 100644 --- a/src/js/ripple/transaction.js +++ b/src/js/ripple/transaction.js @@ -182,8 +182,10 @@ Transaction.prototype.get_fee = function() { Transaction.prototype.complete = function () { var tx_json = this.tx_json; - if (typeof tx_json.Fee === 'undefined' && this.remote.local_fee) { - this.tx_json.Fee = this.remote.fee_tx(this.fee_units()).to_json(); + if (typeof tx_json.Fee === 'undefined') { + if (this.remote.local_fee || !this.remote.trusted) { + this.tx_json.Fee = this.remote.fee_tx(this.fee_units()).to_json(); + } } if (typeof tx_json.SigningPubKey === 'undefined' && (!this.remote || this.remote.local_signing)) { @@ -200,10 +202,7 @@ Transaction.prototype.serialize = function () { }; Transaction.prototype.signing_hash = function () { - var prefix = config.testnet - ? Transaction.HASH_SIGN_TESTNET - : Transaction.HASH_SIGN; - + var prefix = Transaction[config.testnet ? 'HASH_SIGN_TESTNET' : 'HASH_SIGN']; return SerializedObject.from_json(this.tx_json).signing_hash(prefix); }; @@ -223,13 +222,6 @@ Transaction.prototype.sign = function () { this.tx_json.TxnSignature = hex; }; -Transaction.prototype._hasTransactionListeners = function() { - return this.listeners('final').length - || this.listeners('lost').length - || this.listeners('pending').length -}; - - // // Set options for Transactions // @@ -239,7 +231,6 @@ Transaction.prototype._hasTransactionListeners = function() { // "blindly" because the sender has no idea of the actual cost except that is must be less than send max. Transaction.prototype.build_path = function (build) { this._build_path = build; - return this; } @@ -249,28 +240,27 @@ Transaction.prototype.destination_tag = function (tag) { if (tag !== void(0)) { this.tx_json.DestinationTag = tag; } - return this; } Transaction._path_rewrite = function (path) { - var path_new = []; + var props = [ + 'account' + , 'issuer' + , 'currency' + ] - for (var i=0, l=path.length; i rate: In billionths. Transaction.prototype.transfer_rate = function (rate) { @@ -324,7 +310,7 @@ Transaction.prototype.transfer_rate = function (rate) { } return this; -} +}; // Add flags to a transaction. // --> flags: undefined, _flag_, or [ _flags_ ] @@ -339,12 +325,9 @@ Transaction.prototype.set_flags = function (flags) { var flag_set = Array.isArray(flags) ? flags : [ flags ]; - for (var index in flag_set) { - if (!flag_set.hasOwnProperty(index)) continue; - - var flag = flag_set[index]; - - if (flag in transaction_flags) { + for (var i=0, l=flag_set.length; i this._config.maxFee) { -// if (this._timeout) clearTimeout(this._timeout); -// return; -// } -// -// // resubmit everything raising fee if needed -// for (var i=0; i= 8) { - // Lost - tx.emit('error', message); - tx.emit('lost', message); - } else if (dif >= 4) { - // Missing - tx.set_state('client_missing'); - tx.emit('pending', message); - } else { - // Pending - tx.emit('pending', message); - } + if (self._is_not_found(err)) { + var dif = ledger_index - tx.submit_index; + if (dif >= 8) { + // Lost + tx.emit('error', message); + tx.emit('lost', message); + } else if (dif >= 4) { + // Missing + tx.set_state('client_missing'); + tx.emit('missing', message); } else { - // Transaction was found in the ledger, - // consider this transaction successful - if (message.metadata) { - tx.set_state(message.metadata.TransactionResult); - } - tx.emit('success', message); + // Pending + tx.emit('pending', message); } - }); + } else { + // Transaction was found in the ledger, + // consider this transaction successful + if (message.metadata) { + tx.set_state(message.metadata.TransactionResult); + } + tx.emit('success', message); + } + } + + function ledger_closed(message) { + if (!tx.finalized && !checked_ledgers[message.ledger_hash]) { + remote.request_transaction_entry(tx.hash, message.ledger_hash, entry_callback); + } } function transaction_proposed() { @@ -253,10 +324,12 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) { function transaction_finalized() { // Stop checking the ledger remote.removeListener('ledger_closed', ledger_closed); + tx.removeListener('proposed', transaction_proposed); } tx.once('proposed', transaction_proposed); tx.once('final', transaction_finalized); + tx.once('resubmit', transaction_finalized); }; /** @@ -265,19 +338,24 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) { TransactionManager.prototype.submit = function(tx) { // If sequence number is not yet known, defer until it is. + var self = this; + if (!this._next_sequence) { - this.once('sequence_loaded', this.submit.bind(this, tx)); + function resubmit_transaction() { + self.submit(tx); + } + this.once('sequence_loaded', resubmit_transaction); return; } - var seq = this._next_sequence++; - var fee = tx.fee_units(); + tx.tx_json.Sequence = this._next_sequence++; + tx.complete(); - if (fee <= this._config.max_fee) { - tx.tx_json.Sequence = seq; - tx.tx_json.Fee = fee; - tx.complete(); + this._pending.push(tx); + var fee = tx.tx_json.Fee; + + if (fee === void(0) || fee <= this._max_fee) { this._request(tx); } }; diff --git a/src/js/ripple/transactionqueue.js b/src/js/ripple/transactionqueue.js new file mode 100644 index 00000000..ce2f8116 --- /dev/null +++ b/src/js/ripple/transactionqueue.js @@ -0,0 +1,59 @@ + +function TransactionQueue() { + this._queue = [ ]; +} + +TransactionQueue.prototype.length = function() { + return this._queue.length; +}; + +TransactionQueue.prototype.push = function(o) { + return this._queue.push(o); +}; + +TransactionQueue.prototype.hasHash = function(hash) { + return this.indexOf('hash', hash) !== -1; +}; + +TransactionQueue.prototype.hasSequence = function(sequence) { + return this.indexOf('sequence', sequence) !== -1; +}; + +TransactionQueue.prototype.indexOf = function(prop, val) { + var index = -1; + for (var i=0, tx; tx=this._queue[i]; i++) { + if (tx[prop] === val) { + index = i; + break; + } + } + return index; +}; + +TransactionQueue.prototype.removeSequence = function(sequence) { + var result = [ ]; + for (var i=0, tx; tx=this._queue[i]; i++) { + if (!tx.tx_json) continue; + if (tx.tx_json.Sequence !== sequence) + result.push(tx); + } + this._queue = result; +}; + +TransactionQueue.prototype.removeHash = function(hash) { + var result = [ ]; + for (var i=0, tx; tx=this._queue[i]; i++) { + if (!tx.tx_json) continue; + if (tx.hash !== hash) + result.push(tx); + } + this._queue = result; +}; + +TransactionQueue.prototype.forEach = function(fn) { + for (var i=0, tx; tx=this._queue[i]; i++) { + fn(tx, i); + } +}; + +exports.TransactionQueue = TransactionQueue; From 0811dec65cc896e5d213f05158f20a96014b917e Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Wed, 14 Aug 2013 17:21:08 -0700 Subject: [PATCH 30/30] Update server --- src/js/ripple/server.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index be047132..536fb6b8 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -14,7 +14,7 @@ var utils = require('./utils'); function Server(remote, opts) { EventEmitter.call(this); - if (typeof opts !== 'object' || typeof opts.url !== 'string') { + if (typeof opts !== 'object') { throw new Error('Invalid server configuration.'); } @@ -22,6 +22,9 @@ function Server(remote, opts) { this._remote = remote; this._opts = opts; + this._host = opts.host; + this._port = opts.port; + this._secure = typeof opts.secure === Boolean ? opts.secure : true; this._ws = void(0); this._connected = false; this._should_connect = false; @@ -30,6 +33,9 @@ function Server(remote, opts) { this._retry = 0; this._requests = { }; + this._opts.url = (opts.secure ? 'wss://' : 'ws://') + + [ opts.host, opts.port ].join(':'); + this.on('message', function(message) { self._handle_message(message); });