mirror of
https://github.com/Xahau/xahau.js.git
synced 2026-08-23 00:20:52 +00:00
Merge branch 'develop'
This commit is contained in:
2
.npmignore
Normal file
2
.npmignore
Normal file
@@ -0,0 +1,2 @@
|
||||
build
|
||||
deploy
|
||||
118
README.md
118
README.md
@@ -3,9 +3,121 @@ Ripple JavaScript Library - ripple-lib
|
||||
|
||||
This library can connect to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
|
||||
|
||||
Build instructions:
|
||||
* https://ripple.com/wiki/Ripple_JavaScript_library
|
||||
|
||||
For more information:
|
||||
* https://ripple.com
|
||||
* https://ripple.com/wiki
|
||||
|
||||
##Initializing a remote connection
|
||||
|
||||
[ripple-lib.remote](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js) is responsible for managing connections to rippled servers.
|
||||
|
||||
```js
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
var remote = new Remote({
|
||||
trusted: false,
|
||||
servers: [
|
||||
{
|
||||
host: ''
|
||||
, port: 1111,
|
||||
, secure: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
remote.connect();
|
||||
```
|
||||
|
||||
Once a connection is formed to any of the supplied servers, a `connect` event is emitted, indicating that the remote is ready to begin fulfilling requests. When there are no more connected servers to fulfill requests, a `disconnect` event is emitted. If you send requests before ripple-lib is connected to any servers, requests are deferred until the `connect` event is received.
|
||||
|
||||
```js
|
||||
var remote = new Remote({ /* options */ }).connect();
|
||||
remote.request_server_info(function(err, info) { }); // will defer until connected
|
||||
```
|
||||
|
||||
##Remote functions
|
||||
|
||||
Each remote function returns a `Request` object. is object is an `EventEmitter`. You may listen for success or failure events from each request, or provide a callback. Example:
|
||||
|
||||
```js
|
||||
var request = remote.request_server_info();
|
||||
request.on('success', function(res) {
|
||||
//handle success conditions
|
||||
});
|
||||
request.on('error', function(err) {
|
||||
//handle error conditions
|
||||
});
|
||||
request.request();
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```js
|
||||
remote.request_server_info(function(err, res) {
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
**request_server_info([callback])**
|
||||
|
||||
**request_ledger(ledger, [opts], [callback])**
|
||||
|
||||
**request_ledger_hash([callback])**
|
||||
|
||||
**request_ledger_header([callback])**
|
||||
|
||||
**request_ledger_current([callback])**
|
||||
|
||||
**request_ledger_entry(type, [callback])**
|
||||
|
||||
**request_subscribe(streams, [callback])**
|
||||
|
||||
**request_unsubscribe(streams, [callback])**
|
||||
|
||||
**request_transaction_entry(hash, [callback])**
|
||||
|
||||
**request_tx(hash, [callback])**
|
||||
|
||||
**request_account_info(accountID, [callback])**
|
||||
|
||||
**request_account_lines(accountID, account_index, current, [callback])**
|
||||
|
||||
**request_account_offers(accountID, account_index, current, [callback])**
|
||||
|
||||
**request_account_tx(opts, [callback])**
|
||||
|
||||
**request_book_offers(gets, pays, taker, [callback])**
|
||||
|
||||
**request_wallet_accounts(seed, [callback])**
|
||||
|
||||
+ requires trusted **remote
|
||||
|
||||
**request_sign(secret, tx_json, [callback])**
|
||||
|
||||
+ requires trusted **remote
|
||||
|
||||
**request_submit([callback])**
|
||||
|
||||
**request_account_balance(account, current, [callback])**
|
||||
|
||||
**request_account_flags(account, current, [callback])**
|
||||
|
||||
**request_owner_count(account, current, [callback])**
|
||||
|
||||
**request_ripple_balance(account, issuer, currency, current, [callback])**
|
||||
|
||||
**request_ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])**
|
||||
|
||||
**request_unl_list([callback])**
|
||||
|
||||
**request_unl_add(addr, comment, [callback])**
|
||||
|
||||
**request_unl_delete(node, [callback])**
|
||||
|
||||
**request_peers([callback])**
|
||||
|
||||
**request_connect(ip, port, [callback])**
|
||||
|
||||
**transaction()**
|
||||
|
||||
+ returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object
|
||||
|
||||
15
package.json
15
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.7.15",
|
||||
"version": "0.7.17",
|
||||
"description": "Ripple JavaScript client library",
|
||||
"files": [
|
||||
"src/js/ripple/*.js",
|
||||
@@ -13,17 +13,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.2.9",
|
||||
"ws": "~0.4.25",
|
||||
"ws": "~0.4.27",
|
||||
"extend": "~1.1.3",
|
||||
"simple-jsonrpc": "~0.0.2"
|
||||
"simple-jsonrpc": "~0.0.2",
|
||||
"jshint-loader": "~0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-watch": "~0.4.0",
|
||||
"grunt-webpack": "~0.10.2",
|
||||
"grunt-dox": "~0.4.1",
|
||||
"buster": "~0.6.2"
|
||||
"grunt-contrib-watch": "~0.4.4",
|
||||
"grunt-webpack": "~0.10.5",
|
||||
"grunt-dox": "~0.5.0",
|
||||
"buster": "~0.6.12"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/buster/bin/buster test"
|
||||
|
||||
@@ -882,6 +882,8 @@ Amount.prototype.to_text = function (allow_nan) {
|
||||
* @param opts.min_precision {Number} Min. number of digits after dec. point.
|
||||
* @param opts.skip_empty_fraction {Boolean} Don't show fraction if it is zero,
|
||||
* even if min_precision is set.
|
||||
* @param opts.max_sig_digits {Number} Maximum number of significant digits.
|
||||
* Will cut fractional part, but never integer part.
|
||||
* @param opts.group_sep {Boolean|String} Whether to show a separator every n
|
||||
* digits, if a string, that value will be used as the separator. Default: ","
|
||||
* @param opts.group_width {Number} How many numbers will be grouped together,
|
||||
@@ -914,10 +916,38 @@ Amount.prototype.to_human = function (opts)
|
||||
fraction_part = fraction_part.replace(/0*$/, '');
|
||||
|
||||
if (fraction_part.length || !opts.skip_empty_fraction) {
|
||||
// Enforce the maximum number of decimal digits (precision)
|
||||
if ("number" === typeof opts.precision) {
|
||||
fraction_part = fraction_part.slice(0, opts.precision);
|
||||
}
|
||||
|
||||
// Limit the number of significant digits (max_sig_digits)
|
||||
if ("number" === typeof opts.max_sig_digits) {
|
||||
// First, we count the significant digits we have.
|
||||
// A zero in the integer part does not count.
|
||||
var int_is_zero = +int_part === 0;
|
||||
var digits = int_is_zero ? 0 : int_part.length;
|
||||
|
||||
// Don't count leading zeros in the fractional part if the integer part is
|
||||
// zero.
|
||||
var sig_frac = int_is_zero ? fraction_part.replace(/^0*/, '') : fraction_part;
|
||||
digits += sig_frac.length;
|
||||
|
||||
// Now we calculate where we are compared to the maximum
|
||||
var rounding = digits - opts.max_sig_digits;
|
||||
|
||||
// If we're under the maximum we want to cut no (=0) digits
|
||||
rounding = Math.max(rounding, 0);
|
||||
|
||||
// If we're over the maximum we still only want to cut digits from the
|
||||
// fractional part, not from the integer part.
|
||||
rounding = Math.min(rounding, fraction_part.length);
|
||||
|
||||
// Now we cut `rounding` digits off the right.
|
||||
if (rounding > 0) fraction_part = fraction_part.slice(0, -rounding);
|
||||
}
|
||||
|
||||
// Enforce the minimum number of decimal digits (min_precision)
|
||||
if ("number" === typeof opts.min_precision) {
|
||||
while (fraction_part.length < opts.min_precision) {
|
||||
fraction_part += "0";
|
||||
|
||||
@@ -9,35 +9,32 @@
|
||||
// var network = require("./network.js");
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var util = require('util');
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
|
||||
var extend = require('extend');
|
||||
var extend = require('extend');
|
||||
|
||||
var OrderBook = function (remote,
|
||||
currency_gets, issuer_gets,
|
||||
currency_pays, issuer_pays) {
|
||||
var OrderBook = function (remote, currency_gets, issuer_gets, currency_pays, issuer_pays) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._remote = remote;
|
||||
this._currency_gets = currency_gets;
|
||||
this._issuer_gets = issuer_gets;
|
||||
this._issuer_gets = issuer_gets;
|
||||
this._currency_pays = currency_pays;
|
||||
this._issuer_pays = issuer_pays;
|
||||
|
||||
this._subs = 0;
|
||||
this._issuer_pays = issuer_pays;
|
||||
this._subs = 0;
|
||||
|
||||
// We consider ourselves synchronized if we have a current copy of the offers,
|
||||
// we are online and subscribed to updates.
|
||||
this._sync = false;
|
||||
this._sync = false;
|
||||
|
||||
// Offers
|
||||
this._offers = [];
|
||||
this._offers = [];
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
|
||||
@@ -49,10 +46,9 @@ var OrderBook = function (remote,
|
||||
});
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
|
||||
if (~OrderBook.subscribe_events.indexOf(type)) {
|
||||
self._subs -= 1;
|
||||
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._sync = false;
|
||||
self._remote.request_unsubscribe()
|
||||
.books([self.to_json()])
|
||||
@@ -86,8 +82,7 @@ OrderBook.subscribe_events = ['transaction', 'model', 'trade'];
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
OrderBook.prototype._subscribe = function ()
|
||||
{
|
||||
OrderBook.prototype._subscribe = function () {
|
||||
var self = this;
|
||||
self._remote.request_subscribe()
|
||||
.books([self.to_json()], true)
|
||||
@@ -95,26 +90,28 @@ OrderBook.prototype._subscribe = function ()
|
||||
// XXX What now?
|
||||
})
|
||||
.on('success', function (res) {
|
||||
self._sync = true;
|
||||
self._sync = true;
|
||||
self._offers = res.offers;
|
||||
self.emit('model', self._offers);
|
||||
})
|
||||
.request();
|
||||
};
|
||||
|
||||
OrderBook.prototype.to_json = function ()
|
||||
{
|
||||
OrderBook.prototype.to_json = function () {
|
||||
var json = {
|
||||
"taker_gets": {
|
||||
"currency": this._currency_gets
|
||||
'taker_gets': {
|
||||
'currency': this._currency_gets
|
||||
},
|
||||
"taker_pays": {
|
||||
"currency": this._currency_pays
|
||||
'taker_pays': {
|
||||
'currency': this._currency_pays
|
||||
}
|
||||
};
|
||||
|
||||
if (this._currency_gets !== "XRP") json["taker_gets"]["issuer"] = this._issuer_gets;
|
||||
if (this._currency_pays !== "XRP") json["taker_pays"]["issuer"] = this._issuer_pays;
|
||||
if (this._currency_gets !== 'XRP')
|
||||
json['taker_gets']['issuer'] = this._issuer_gets;
|
||||
|
||||
if (this._currency_pays !== 'XRP')
|
||||
json['taker_pays']['issuer'] = this._issuer_pays;
|
||||
|
||||
return json;
|
||||
};
|
||||
@@ -125,78 +122,88 @@ OrderBook.prototype.to_json = function ()
|
||||
* Note: This only checks whether the parameters (currencies and issuer) are
|
||||
* syntactically valid. It does not check anything against the ledger.
|
||||
*/
|
||||
OrderBook.prototype.is_valid = function ()
|
||||
{
|
||||
OrderBook.prototype.is_valid = function () {
|
||||
// XXX Should check for same currency (non-native) && same issuer
|
||||
return (
|
||||
Currency.is_valid(this._currency_pays) &&
|
||||
(this._currency_pays === "XRP" || UInt160.is_valid(this._issuer_pays)) &&
|
||||
(this._currency_pays === 'XRP' || UInt160.is_valid(this._issuer_pays)) &&
|
||||
Currency.is_valid(this._currency_gets) &&
|
||||
(this._currency_gets === "XRP" || UInt160.is_valid(this._issuer_gets)) &&
|
||||
!(this._currency_pays === "XRP" && this._currency_gets === "XRP")
|
||||
(this._currency_gets === 'XRP' || UInt160.is_valid(this._issuer_gets)) &&
|
||||
!(this._currency_pays === 'XRP' && this._currency_gets === 'XRP')
|
||||
);
|
||||
};
|
||||
|
||||
OrderBook.prototype.trade = function(type) {
|
||||
var tradeStr = '0'
|
||||
+ (this['_currency_' + type] === 'XRP') ? '' : '/'
|
||||
+ this['_currency_' + type ] + '/'
|
||||
+ this['_issuer_' + type];
|
||||
return Amount.from_json(tradeStr);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
OrderBook.prototype.notifyTx = function (message)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
var changed = false;
|
||||
|
||||
var trade_gets = Amount.from_json("0" + ((this._currency_gets === 'XRP') ? "" :
|
||||
"/" + this._currency_gets +
|
||||
"/" + this._issuer_gets));
|
||||
var trade_pays = Amount.from_json("0" + ((this._currency_pays === 'XRP') ? "" :
|
||||
"/" + this._currency_pays +
|
||||
"/" + this._issuer_pays));
|
||||
OrderBook.prototype.notifyTx = function (message) {
|
||||
var self = this;
|
||||
var changed = false;
|
||||
var trade_gets = this.trade('gets');
|
||||
var trade_pays = this.trade('pays');
|
||||
|
||||
message.mmeta.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
|
||||
var i, l, offer;
|
||||
if (an.diffType === 'DeletedNode' ||
|
||||
an.diffType === 'ModifiedNode') {
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
if (offer.index === an.ledgerIndex) {
|
||||
if (an.diffType === 'DeletedNode') {
|
||||
self._offers.splice(i, 1);
|
||||
|
||||
switch(an.diffType) {
|
||||
case 'DeletedNode':
|
||||
case 'ModifiedNode':
|
||||
var deletedNode = an.diffType === 'DeletedNode';
|
||||
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
if (offer.index === an.ledgerIndex) {
|
||||
if (deletedNode) {
|
||||
self._offers.splice(i, 1);
|
||||
} else {
|
||||
extend(offer, an.fieldsFinal);
|
||||
}
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
else extend(offer, an.fieldsFinal);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't want to count a OfferCancel as a trade
|
||||
if (message.transaction.TransactionType === "OfferCancel") return;
|
||||
// We don't want to count a OfferCancel as a trade
|
||||
if (message.transaction.TransactionType === 'OfferCancel') return;
|
||||
|
||||
trade_gets = trade_gets.add(an.fieldsPrev.TakerGets);
|
||||
trade_pays = trade_pays.add(an.fieldsPrev.TakerPays);
|
||||
if (an.diffType === 'ModifiedNode') {
|
||||
trade_gets = trade_gets.subtract(an.fieldsFinal.TakerGets);
|
||||
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
|
||||
}
|
||||
} else if (an.diffType === 'CreatedNode') {
|
||||
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
var priceItem = Amount.from_json(offer.TakerPays).ratio_human(offer.TakerGets);
|
||||
trade_gets = trade_gets.add(an.fieldsPrev.TakerGets);
|
||||
trade_pays = trade_pays.add(an.fieldsPrev.TakerPays);
|
||||
|
||||
if (price.compareTo(priceItem) <= 0) {
|
||||
var obj = an.fields;
|
||||
obj.index = an.ledgerIndex;
|
||||
self._offers.splice(i, 0, an.fields);
|
||||
changed = true;
|
||||
break;
|
||||
if (!deletedNode) {
|
||||
trade_gets = trade_gets.subtract(an.fieldsFinal.TakerGets);
|
||||
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CreatedNode':
|
||||
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
|
||||
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
var priceItem = Amount.from_json(offer.TakerPays).ratio_human(offer.TakerGets);
|
||||
|
||||
if (price.compareTo(priceItem) <= 0) {
|
||||
var obj = an.fields;
|
||||
obj.index = an.ledgerIndex;
|
||||
self._offers.splice(i, 0, an.fields);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,17 +225,13 @@ OrderBook.prototype.notifyTx = function (message)
|
||||
*
|
||||
* If the data is available immediately, the callback may be called synchronously.
|
||||
*/
|
||||
OrderBook.prototype.offers = function (callback)
|
||||
{
|
||||
OrderBook.prototype.offers = function (callback) {
|
||||
var self = this;
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
if (typeof callback === 'function') {
|
||||
if (this._sync) {
|
||||
callback(this._offers);
|
||||
} else {
|
||||
this.once('model', function (offers) {
|
||||
callback(offers);
|
||||
});
|
||||
this.once('model', callback);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -240,11 +243,10 @@ OrderBook.prototype.offers = function (callback)
|
||||
* Usually, this will just be an empty array if the order book hasn't been
|
||||
* loaded yet. But this accessor may be convenient in some circumstances.
|
||||
*/
|
||||
OrderBook.prototype.offersSync = function ()
|
||||
{
|
||||
OrderBook.prototype.offersSync = function () {
|
||||
return this._offers;
|
||||
};
|
||||
|
||||
exports.OrderBook = OrderBook;
|
||||
exports.OrderBook = OrderBook;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ var extend = require('extend'),
|
||||
|
||||
var amount = require('./amount'),
|
||||
UInt160 = amount.UInt160,
|
||||
UInt256 = require('./uint256').UInt256,
|
||||
Amount = amount.Amount,
|
||||
Currency= amount.Currency;
|
||||
|
||||
@@ -109,8 +110,8 @@ var STHash128 = exports.Hash128 = new SerializedType({
|
||||
|
||||
var STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Hash256 not implemented");
|
||||
var hash = UInt256.from_json(val);
|
||||
this.serialize_hex(so, hash.to_hex());
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var WebSocket = require('ws');
|
||||
var utils = require('./utils');
|
||||
|
||||
/**
|
||||
* Server
|
||||
* @constructor Server
|
||||
* @param remote The Remote object
|
||||
* @param cfg Configuration parameters.
|
||||
*
|
||||
* Options must contain `url` to
|
||||
* WebSocket server
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} remote
|
||||
* @param {Object} opts
|
||||
*/
|
||||
* Keys for cfg:
|
||||
* url
|
||||
*/
|
||||
|
||||
function Server(remote, opts) {
|
||||
var Server = function (remote, opts) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (typeof opts !== 'object' || typeof opts.url !== 'string') {
|
||||
@@ -53,8 +50,7 @@ util.inherits(Server, EventEmitter);
|
||||
* Our requirements are that the server can process transactions and notify
|
||||
* us of changes.
|
||||
*/
|
||||
|
||||
Server.online_states = [
|
||||
Server.online_states = [
|
||||
'syncing'
|
||||
, 'tracking'
|
||||
, 'proposing'
|
||||
@@ -62,24 +58,27 @@ Server.online_states = [
|
||||
, 'full'
|
||||
];
|
||||
|
||||
/**
|
||||
* Determine if a server status qualifies
|
||||
* as 'online'
|
||||
*
|
||||
* @param {String} status
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.is_online = function(status) {
|
||||
Server.prototype._is_online = function (status) {
|
||||
return Server.online_states.indexOf(status) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to WebSocket server
|
||||
*/
|
||||
Server.prototype._set_state = function (state) {
|
||||
if (state !== this._state) {
|
||||
this._state = state;
|
||||
|
||||
Server.prototype.connect = function() {
|
||||
this.emit('state', state);
|
||||
|
||||
if (state === 'online') {
|
||||
this._connected = true;
|
||||
this.emit('connect');
|
||||
} else if (state === 'offline') {
|
||||
this._connected = false;
|
||||
this.emit('disconnect');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype.connect = function () {
|
||||
var self = this;
|
||||
|
||||
// We don't connect if we believe we're already connected. This means we have
|
||||
@@ -88,22 +87,21 @@ Server.prototype.connect = function() {
|
||||
// we will automatically reconnect.
|
||||
if (this._connected === true) 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.
|
||||
var WebSocket = require('ws');
|
||||
var ws = this._ws = new WebSocket(this._opts.url);
|
||||
|
||||
this._should_connect = true;
|
||||
|
||||
self.emit('connecting');
|
||||
|
||||
ws.onopen = function() {
|
||||
ws.onopen = function () {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws !== self._ws) return;
|
||||
|
||||
@@ -114,13 +112,11 @@ Server.prototype.connect = function() {
|
||||
self.request(request);
|
||||
};
|
||||
|
||||
ws.onerror = function(e) {
|
||||
ws.onerror = function (e) {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws !== self._ws) return;
|
||||
|
||||
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
|
||||
@@ -140,13 +136,11 @@ Server.prototype.connect = function() {
|
||||
};
|
||||
|
||||
// Failure to open.
|
||||
ws.onclose = function() {
|
||||
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);
|
||||
}
|
||||
if (self._remote.trace) console.log('server: onclose: %s', ws.readyState);
|
||||
|
||||
handleConnectionClose();
|
||||
};
|
||||
@@ -156,17 +150,16 @@ Server.prototype.connect = function() {
|
||||
self._set_state('offline');
|
||||
|
||||
// Prevent additional events from this socket
|
||||
ws.removeAllListeners();
|
||||
ws.on('error', function() {});
|
||||
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function () {};
|
||||
|
||||
// Should we be connected?
|
||||
if (!self._should_connect) return;
|
||||
|
||||
// Delay and retry.
|
||||
self._retry += 1;
|
||||
|
||||
self._retry_timer = setTimeout(function retryTimeout() {
|
||||
self._retry += 1;
|
||||
self._retry_timer = setTimeout(function () {
|
||||
if (self._remote.trace) console.log('server: retry');
|
||||
|
||||
if (!self._should_connect) return;
|
||||
self.connect();
|
||||
}, self._retry < 40
|
||||
@@ -176,20 +169,14 @@ Server.prototype.connect = function() {
|
||||
: self._retry < 40+60+60
|
||||
? 10*1000 // Then, for 10 minutes: once every 10 seconds
|
||||
: 30*1000); // Then: once every 30 seconds
|
||||
};
|
||||
}
|
||||
|
||||
ws.onmessage = function(msg) {
|
||||
ws.onmessage = function (msg) {
|
||||
self.emit('message', msg.data);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from WebSocket server
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.disconnect = function() {
|
||||
Server.prototype.disconnect = function () {
|
||||
this._should_connect = false;
|
||||
this._set_state('offline');
|
||||
if (this._ws) {
|
||||
@@ -197,27 +184,14 @@ Server.prototype.disconnect = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send stringified message to WebSocket server
|
||||
*
|
||||
* @param {Object} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.send = function(message) {
|
||||
if (this._ws) {
|
||||
this._ws.send(JSON.stringify(message));
|
||||
}
|
||||
Server.prototype.send_message = function (message) {
|
||||
this._ws.send(JSON.stringify(message));
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a Request object to this server.
|
||||
*
|
||||
* @param {Object} request
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.request = function(request) {
|
||||
Server.prototype.request = function (request) {
|
||||
var self = this;
|
||||
|
||||
// Only bother if we are still connected.
|
||||
@@ -229,19 +203,19 @@ Server.prototype.request = function(request) {
|
||||
// Advance message ID
|
||||
self._id++;
|
||||
|
||||
if (self._connected || (request.message.command === 'subscribe'
|
||||
&& self._ws.readyState === 1)) {
|
||||
if (self._connected || (request.message.command === 'subscribe' && self._ws.readyState === 1)) {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: request: %s', request.message);
|
||||
}
|
||||
self.send(request.message);
|
||||
|
||||
self.send_message(request.message);
|
||||
} else {
|
||||
// XXX There are many ways to make self smarter.
|
||||
self.once('connect', function () {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: request: %s', request.message);
|
||||
}
|
||||
self.send(request.message);
|
||||
self.send_message(request.message);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -251,111 +225,56 @@ Server.prototype.request = function(request) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set server state
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* set_state('online')
|
||||
* set_state('offline')
|
||||
*
|
||||
* @param {String} state
|
||||
* @api private
|
||||
*/
|
||||
Server.prototype._handle_message = function (json) {
|
||||
var self = this;
|
||||
|
||||
Server.prototype._set_state = function(state) {
|
||||
if (state !== this._state) {
|
||||
this._state = state;
|
||||
|
||||
this.emit('state', state);
|
||||
|
||||
if (state === 'online') {
|
||||
this._connected = true;
|
||||
this.emit('connect');
|
||||
} else if (state === 'offline') {
|
||||
this._connected = false;
|
||||
this.emit('disconnect');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle WebSocket message
|
||||
*
|
||||
* @param {String} json
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handle_message = function(json) {
|
||||
var self = this;
|
||||
var unexpected = false;
|
||||
var message;
|
||||
|
||||
try { message = JSON.parse(json); } catch(exception) { }
|
||||
try {
|
||||
message = JSON.parse(json);
|
||||
} catch(exception) { return; }
|
||||
|
||||
var unexpected = typeof message !== 'object' || typeof message.type !== 'string';
|
||||
switch(message.type) {
|
||||
case 'response':
|
||||
// A response to a request.
|
||||
var request = self._requests[message.id];
|
||||
|
||||
if (unexpected) {
|
||||
// We received a malformed response from the server
|
||||
}
|
||||
delete self._requests[message.id];
|
||||
|
||||
if (!unexpected) {
|
||||
switch (message.type) {
|
||||
case 'response':
|
||||
// A response to a request.
|
||||
var request = self._requests[message.id];
|
||||
if (!request) {
|
||||
if (self._remote.trace) utils.logObject('server: UNEXPECTED: %s', message);
|
||||
} else if ('success' === message.status) {
|
||||
if (self._remote.trace) utils.logObject('server: response: %s', message);
|
||||
|
||||
delete self._requests[message.id];
|
||||
request.emit('success', message.result);
|
||||
|
||||
if (!request) {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: UNEXPECTED: %s', message);
|
||||
}
|
||||
} else if (message.status === 'success') {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: response: %s', message);
|
||||
}
|
||||
[ self, self._remote ].forEach(function(emitter) {
|
||||
emitter.emit('response_' + request.message.command, message.result, request, message);
|
||||
});
|
||||
} else if (message.error) {
|
||||
if (self._remote.trace) utils.logObject('server: error: %s', message);
|
||||
|
||||
request.emit('success', message.result);
|
||||
request.emit('error', {
|
||||
'error' : 'remoteError',
|
||||
'error_message' : 'Remote reported an error.',
|
||||
'remote' : message
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
[ self, self._remote ].forEach(function(emitter) {
|
||||
emitter.emit('response_' + request.message.command, message.result, request, message);
|
||||
});
|
||||
} else if (message.error) {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: error: %s', message);
|
||||
}
|
||||
|
||||
request.emit('error', {
|
||||
'error' : 'remoteError',
|
||||
'error_message' : 'Remote reported an error.',
|
||||
'remote' : message
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'serverStatus':
|
||||
// This message is only received when online.
|
||||
// As we are connected, it is the definitive final state.
|
||||
self._set_state(self.is_online(message.server_status) ? 'online' : 'offline');
|
||||
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');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle subscribe response
|
||||
*
|
||||
* @param {Object} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handle_response_subscribe = function(message) {
|
||||
Server.prototype._handle_response_subscribe = function (message) {
|
||||
var self = this;
|
||||
|
||||
self._server_status = message.server_status;
|
||||
|
||||
if (self.is_online(message.server_status)) {
|
||||
if (self._is_online(message.server_status)) {
|
||||
self._set_state('online');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -72,43 +72,44 @@ var Transaction = function (remote) {
|
||||
this.remote = remote;
|
||||
this._secret = undefined;
|
||||
this._build_path = false;
|
||||
this.tx_json = { // Transaction data.
|
||||
'Flags' : 0, // XXX Would be nice if server did not require this.
|
||||
|
||||
// Transaction data.
|
||||
this.tx_json = {
|
||||
'Flags' : 0, // XXX Would be nice if server did not require this.
|
||||
};
|
||||
|
||||
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.on('success', function (message) {
|
||||
if (message.engine_result) {
|
||||
self.hash = message.tx_json.hash;
|
||||
if (message.engine_result) {
|
||||
self.hash = message.tx_json.hash;
|
||||
|
||||
self.set_state('client_proposed');
|
||||
self.set_state('client_proposed');
|
||||
|
||||
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.
|
||||
});
|
||||
}
|
||||
});
|
||||
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');
|
||||
});
|
||||
// Might want to give more detailed information.
|
||||
self.set_state('remoteError');
|
||||
});
|
||||
};
|
||||
|
||||
util.inherits(Transaction, EventEmitter);
|
||||
|
||||
// XXX This needs to be determined from the network.
|
||||
Transaction.fees = {
|
||||
'default' : Amount.from_json("10"),
|
||||
'nickname_create' : Amount.from_json("1000"),
|
||||
'offer' : Amount.from_json("10"),
|
||||
'default' : 10,
|
||||
};
|
||||
|
||||
Transaction.flags = {
|
||||
@@ -194,15 +195,17 @@ Transaction.prototype.set_state = function (state) {
|
||||
Transaction.prototype.complete = function () {
|
||||
var tx_json = this.tx_json;
|
||||
|
||||
if (undefined === tx_json.Fee && this.remote.local_fee) {
|
||||
tx_json.Fee = Transaction.fees['default'].to_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 (undefined === tx_json.SigningPubKey && (!this.remote || this.remote.local_signing)) {
|
||||
if ("undefined" === typeof tx_json.SigningPubKey && (!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();
|
||||
}
|
||||
|
||||
return this.tx_json;
|
||||
};
|
||||
|
||||
Transaction.prototype.serialize = function () {
|
||||
@@ -211,23 +214,28 @@ Transaction.prototype.serialize = function () {
|
||||
|
||||
Transaction.prototype.signing_hash = function () {
|
||||
var prefix = config.testnet
|
||||
? Transaction.HASH_SIGN_TESTNET
|
||||
: Transaction.HASH_SIGN;
|
||||
? Transaction.HASH_SIGN_TESTNET
|
||||
: Transaction.HASH_SIGN;
|
||||
|
||||
return SerializedObject.from_json(this.tx_json).signing_hash(prefix);
|
||||
};
|
||||
|
||||
Transaction.prototype.sign = function () {
|
||||
var seed = Seed.from_json(this._secret),
|
||||
hash = this.signing_hash();
|
||||
|
||||
var key = seed.get_key(this.tx_json.Account),
|
||||
sig = key.sign(hash, 0),
|
||||
hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
||||
var seed = Seed.from_json(this._secret);
|
||||
var hash = this.signing_hash();
|
||||
var key = seed.get_key(this.tx_json.Account);
|
||||
var sig = key.sign(hash, 0);
|
||||
var hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
||||
|
||||
this.tx_json.TxnSignature = hex;
|
||||
};
|
||||
|
||||
Transaction.prototype._hasTransactionListeners = function() {
|
||||
return this.listeners('final').length
|
||||
|| this.listeners('lost').length
|
||||
|| 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.
|
||||
@@ -242,18 +250,25 @@ Transaction.prototype.sign = function () {
|
||||
// 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 = callback;
|
||||
this.callback = typeof callback === 'function'
|
||||
? callback
|
||||
: function(){};
|
||||
|
||||
if ('string' !== typeof tx_json.Account)
|
||||
{
|
||||
(this.callback || this.emit)('error', {
|
||||
'error' : 'tejInvalidAccount',
|
||||
'error_message' : 'Bad account.'
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -261,143 +276,142 @@ Transaction.prototype.submit = function (callback) {
|
||||
|
||||
this.complete();
|
||||
|
||||
if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) {
|
||||
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
|
||||
//console.log('Callback or has listeners');
|
||||
|
||||
this.submit_index = this.remote._ledger_current_index;
|
||||
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
|
||||
|
||||
// When a ledger closes, look for the result.
|
||||
var on_ledger_closed = function (message) {
|
||||
var ledger_hash = message.ledger_hash;
|
||||
var ledger_index = message.ledger_index;
|
||||
var stop = false;
|
||||
this.submit_index = this.remote._ledger_current_index;
|
||||
|
||||
// XXX make sure self.hash is available.
|
||||
self.remote.request_transaction_entry(self.hash)
|
||||
.ledger_hash(ledger_hash)
|
||||
.on('success', function (message) {
|
||||
if (self.finalized) return;
|
||||
// When a ledger closes, look for the result.
|
||||
function on_ledger_closed(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;
|
||||
var ledger_hash = message.ledger_hash;
|
||||
var ledger_index = message.ledger_index;
|
||||
var stop = false;
|
||||
|
||||
if (self.callback)
|
||||
self.callback(message.metadata.TransactionResult, message);
|
||||
})
|
||||
.on('error', function (message) {
|
||||
if (self.finalized) return;
|
||||
// XXX make sure self.hash is available.
|
||||
var transaction_entry = self.remote.request_transaction_entry(self.hash)
|
||||
|
||||
if ('remoteError' === message.error
|
||||
&& 'transactionNotFound' === message.remote.error) {
|
||||
if (self.submit_index + SUBMIT_LOST < ledger_index) {
|
||||
self.set_state('client_lost'); // Gave up.
|
||||
self.emit('lost');
|
||||
transaction_entry.ledger_hash(ledger_hash)
|
||||
|
||||
if (self.callback)
|
||||
self.callback('tejLost', message);
|
||||
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);
|
||||
});
|
||||
|
||||
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.
|
||||
})
|
||||
.request();
|
||||
};
|
||||
transaction_entry.on('error', function (message) {
|
||||
if (self.finalized) return;
|
||||
|
||||
this.remote.on('ledger_closed', on_ledger_closed);
|
||||
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.
|
||||
});
|
||||
|
||||
if (this.callback) {
|
||||
this.on('error', function (message) {
|
||||
self.callback(message.error, message);
|
||||
});
|
||||
}
|
||||
}
|
||||
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');
|
||||
|
||||
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.
|
||||
this.remote.account_seq_cache(self.tx_json.Account, false)
|
||||
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) {
|
||||
// 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();
|
||||
// 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.on('success', function (res) {
|
||||
if (!res || "string" !== typeof res.engine_result) return;
|
||||
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;
|
||||
|
||||
switch (res.engine_result.slice(0, 3)) {
|
||||
// Synchronous local error
|
||||
case 'tej':
|
||||
self.remote.account_seq(self.tx_json.Account, 'REWIND');
|
||||
break;
|
||||
// XXX: What do we do in case of ter?
|
||||
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();
|
||||
case 'ter':
|
||||
// XXX: What do we do in case of ter?
|
||||
break;
|
||||
|
||||
// XXX Hotfix. This only works if no other transactions are pending.
|
||||
self.remote.account_seq(self.tx_json.Account, 'REWIND');
|
||||
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 successes and errors.
|
||||
request.on('success', function (message) {
|
||||
self.emit('success', message);
|
||||
});
|
||||
request.on('error', function (message) {
|
||||
self.emit('error', message);
|
||||
});
|
||||
// Forward events
|
||||
request.emit = this.emit.bind(this);
|
||||
|
||||
if (!this._secret && !this.tx_json.Signature) {
|
||||
this.emit('error', {
|
||||
finish({
|
||||
'result' : 'tejSecretUnknown',
|
||||
'result_message' : "Could not sign transactions because we."
|
||||
});
|
||||
@@ -407,11 +421,10 @@ Transaction.prototype.submit = function (callback) {
|
||||
request.tx_blob(this.serialize().to_hex());
|
||||
} else {
|
||||
if (!this.remote.trusted) {
|
||||
this.emit('error', {
|
||||
finish({
|
||||
'result' : 'tejServerUntrusted',
|
||||
'result_message' : "Attempt to give a secret to an untrusted server."
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
request.secret(this._secret);
|
||||
@@ -440,8 +453,9 @@ 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 (undefined !== tag)
|
||||
this.tx_json.DestinationTag = tag;
|
||||
if (tag !== undefined) {
|
||||
this.tx_json.DestinationTag = tag;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -491,8 +505,9 @@ Transaction.prototype.secret = function (secret) {
|
||||
}
|
||||
|
||||
Transaction.prototype.send_max = function (send_max) {
|
||||
if (send_max)
|
||||
this.tx_json.SendMax = Amount.json_rewrite(send_max);
|
||||
if (send_max) {
|
||||
this.tx_json.SendMax = Amount.json_rewrite(send_max);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -500,8 +515,9 @@ Transaction.prototype.send_max = function (send_max) {
|
||||
// tag should be undefined or a 32 bit integer.
|
||||
// YYY Add range checking for tag.
|
||||
Transaction.prototype.source_tag = function (tag) {
|
||||
if (undefined !== tag)
|
||||
this.tx_json.SourceTag = tag;
|
||||
if (tag) {
|
||||
this.tx_json.SourceTag = tag;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -510,8 +526,9 @@ Transaction.prototype.source_tag = function (tag) {
|
||||
Transaction.prototype.transfer_rate = function (rate) {
|
||||
this.tx_json.TransferRate = Number(rate);
|
||||
|
||||
if (this.tx_json.TransferRate < 1e9)
|
||||
throw 'invalidTransferRate';
|
||||
if (this.tx_json.TransferRate < 1e9) {
|
||||
throw new Error('invalidTransferRate');
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -520,24 +537,26 @@ Transaction.prototype.transfer_rate = function (rate) {
|
||||
// --> flags: undefined, _flag_, or [ _flags_ ]
|
||||
Transaction.prototype.set_flags = function (flags) {
|
||||
if (flags) {
|
||||
var transaction_flags = Transaction.flags[this.tx_json.TransactionType];
|
||||
var transaction_flags = Transaction.flags[this.tx_json.TransactionType];
|
||||
|
||||
if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction.
|
||||
this.tx_json.Flags = 0;
|
||||
// We plan to not define this field on new Transaction.
|
||||
if (this.tx_json.Flags === undefined) {
|
||||
this.tx_json.Flags = 0;
|
||||
}
|
||||
|
||||
var flag_set = 'object' === typeof flags ? flags : [ flags ];
|
||||
var flag_set = Array.isArray(flags) ? flags : [ flags ];
|
||||
|
||||
for (var index in flag_set) {
|
||||
if (!flag_set.hasOwnProperty(index)) continue;
|
||||
for (var index in flag_set) {
|
||||
if (!flag_set.hasOwnProperty(index)) continue;
|
||||
|
||||
var flag = flag_set[index];
|
||||
var flag = flag_set[index];
|
||||
|
||||
if (flag in transaction_flags) {
|
||||
this.tx_json.Flags += transaction_flags[flag];
|
||||
} else {
|
||||
// XXX Immediately report an error or mark it.
|
||||
}
|
||||
if (flag in transaction_flags) {
|
||||
this.tx_json.Flags += transaction_flags[flag];
|
||||
} else {
|
||||
// XXX Immediately report an error or mark it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -597,17 +616,15 @@ Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expi
|
||||
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
|
||||
? expiration.getTime()
|
||||
: Number(expiration);
|
||||
}
|
||||
|
||||
if (expiration)
|
||||
this.tx_json.Expiration = Date === expiration.constructor
|
||||
? expiration.getTime()
|
||||
: Number(expiration);
|
||||
|
||||
if (cancel_sequence)
|
||||
this.tx_json.OfferSequence = Number(cancel_sequence);
|
||||
if (cancel_sequence) {
|
||||
this.tx_json.OfferSequence = Number(cancel_sequence);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -664,7 +681,7 @@ Transaction.prototype.ripple_line_set = function (src, limit, quality_in, qualit
|
||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||
|
||||
// Allow limit of 0 through.
|
||||
if (undefined !== limit)
|
||||
if (limit !== undefined)
|
||||
this.tx_json.LimitAmount = Amount.json_rewrite(limit);
|
||||
|
||||
if (quality_in)
|
||||
@@ -689,6 +706,20 @@ 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"];
|
||||
};
|
||||
|
||||
exports.Transaction = Transaction;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
Reference in New Issue
Block a user