Fixes and cleanup for Remote, update readme

This commit is contained in:
wltsmrz
2013-07-13 05:31:40 +09:00
parent 9ec72ee8c5
commit 9550edab9b
5 changed files with 292 additions and 186 deletions

103
README.md
View File

@@ -3,9 +3,108 @@ 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:
##Building
* https://ripple.com/wiki/Ripple_JavaScript_library
For more information:
##See also
* 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({
servers: [
{
host: ''
, port: 1111,
, secure: true
}
]
});
```
##Remote functions
Each remote function returns a `Request` object. This 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) {
});
```
**remote.request_server_info([callback])**
**remote.request_ledger(ledger, [opts], [callback])**
**remote.request_ledger_hash([callback])**
**remote.request_ledger_header([callback])**
**remote.request_ledger_current([callback])**
**remote.request_ledger_entry(type, [callback])**
**remote.request_subscribe(streams, [callback])**
**remote.request_unsubscribe(streams, [callback])**
**remote.request_transaction_entry(hash, [callback])**
**remote.request_tx(hash, [callback])**
**remote.request_account_info(accountID, [callback])**
**remote.request_account_lines(accountID, account_index, current, [callback])**
**remote.request_account_offers(accountID, account_index, current, [callback])**
**remote.request_account_tx(opts, [callback])**
**remote.request_book_offers(gets, pays, taker, [callback])**
**remote.request_wallet_accounts(seed, [callback])**
**remote.request_sign(secret, tx_json, [callback])**
**remote.request_submit([callback])**
**remote.request_account_balance(account, current, [callback])**
**remote.request_account_flags(account, current, [callback])**
**remote.request_owner_count(account, current, [callback])**
**remote.request_ripple_balance(account, issuer, currency, current, [callback])**
**remote.request_ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])**
**remote.request_unl_list([callback])**
**remote.request_unl_add(addr, comment, [callback])**
**remote.request_unl_delete(node, [callback])**
**remote.request_peers([callback])**
**remote.request_connect(ip, port, [callback])**
**remote.transaction()**

View File

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

View File

@@ -37,16 +37,13 @@ var sjcl = require('../../../build/sjcl');
// 'remoteError'
// 'remoteUnexpected'
// 'remoteDisconnected'
var Request = function (remote, command) {
function Request(remote, command) {
EventEmitter.call(this);
var self = this;
this.remote = remote;
this.requested = false;
this.message = {
'command' : command,
'id' : undefined,
command : command,
id : void(0)
};
};
@@ -67,6 +64,7 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) {
this.once(errorEvent || 'error', callback);
this.request();
}
return this;
};
@@ -143,8 +141,8 @@ Request.prototype.index = function (hash) {
// --> 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
account: UInt160.json_rewrite(account),
seq: seq
};
return this;
@@ -228,20 +226,20 @@ Request.prototype.books = function (books, snapshot) {
function processSide(side) {
if (!book[side]) throw new Error('Missing '+side);
var obj = {};
obj['currency'] = Currency.json_rewrite(book[side]['currency']);
if (obj['currency'] !== 'XRP') {
obj.issuer = UInt160.json_rewrite(book[side]['issuer']);
var obj = json[side] = {
currency: Currency.json_rewrite(book[side].currency)
};
if (obj.currency !== 'XRP') {
obj.issuer = UInt160.json_rewrite(book[side].issuer);
}
json[side] = obj;
}
processSide('taker_gets');
processSide('taker_pays');
if (snapshot || book['snapshot']) json['snapshot'] = true;
if (book['both']) json['both'] = true;
if (snapshot) json.snapshot = true;
if (book.both) json.both = true;
procBooks.push(json);
}
@@ -286,7 +284,7 @@ Request.prototype.books = function (books, snapshot) {
@param trace
*/
var Remote = function (opts, trace) {
function Remote(opts, trace) {
EventEmitter.call(this);
var self = this;
@@ -321,7 +319,6 @@ var Remote = function (opts, trace) {
this._connection_count = 0;
this._connected = false;
this._last_tx = null;
// Local signing implies local fees and sequences
@@ -661,6 +658,7 @@ Remote.prototype._server_is_available = function (server) {
Remote.prototype._next_server = function () {
var result = null;
for (var i=0; i<this._servers.length; i++) {
var server = this._servers[i];
if (this._server_is_available(server)) {
@@ -668,17 +666,20 @@ Remote.prototype._next_server = function () {
break;
}
}
return result;
};
Remote.prototype._get_server = function () {
var server;
if (this._server_is_available(this._primary_server)) {
server = this._primary_server;
} else {
server = this._next_server();
if (server) this._set_primary_server(server);
}
return server;
};
@@ -716,7 +717,7 @@ Remote.prototype.request_ledger = function (ledger, opts, callback) {
request.message.ledger = ledger;
}
if ('object' == typeof opts) {
if (typeof opts === 'object') {
if (opts.full)
request.message.full = true;
@@ -728,9 +729,7 @@ Remote.prototype.request_ledger = function (ledger, opts, callback) {
if (opts.accounts)
request.message.accounts = true;
}
// DEPRECATED:
else if (opts) {
} else if (opts) { // DEPRECATED:
console.log('request_ledger: full parameter is deprecated');
request.message.full = true;
}
@@ -841,10 +840,7 @@ Remote.prototype.request_subscribe = function (streams, callback) {
var request = new Request(this, 'subscribe');
if (streams) {
if (!Array.isArray(streams)) {
streams = [ streams ];
}
request.message.streams = streams;
request.message.streams = Array.isArray(streams) ? streams : [ streams ];
}
return request.callback(callback);
@@ -855,10 +851,7 @@ Remote.prototype.request_unsubscribe = function (streams, callback) {
var request = new Request(this, 'unsubscribe');
if (streams) {
if (!Array.isArray(streams)) {
streams = [ streams ];
}
request.message.streams = streams;
request.message.streams = Array.isArray(streams) ? streams : [ streams ];
}
return request.callback(callback);
@@ -1247,7 +1240,7 @@ 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);
var account = UInt160.json_rewrite(account);
delete this.ledgers.current.account_root[account];
};
@@ -1349,8 +1342,8 @@ Remote.prototype.request_unl_delete = function (node, callback) {
return request.callback(callback);
};
Remote.prototype.request_peers = function () {
return new Request(this, 'peers');
Remote.prototype.request_peers = function (callback) {
return new Request(this, 'peers').callback(callback);
};
Remote.prototype.request_connect = function (ip, port, callback) {

View File

@@ -184,6 +184,10 @@ Server.prototype.disconnect = function () {
}
};
Server.prototype.send_message = function (message) {
this._ws.send(JSON.stringify(message));
};
/**
* Submit a Request object to this server.
*/
@@ -199,20 +203,19 @@ Server.prototype.request = function (request) {
// Advance message ID
self._id++;
if (self._state === 'online' ||
(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._ws.send(JSON.stringify(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._ws.send(JSON.stringify(request.message));
self.send_message(request.message);
});
}
} else {

View File

@@ -206,6 +206,8 @@ Transaction.prototype.complete = function () {
var key = seed.get_key(this.tx_json.Account);
tx_json.SigningPubKey = key.to_hex_pub();
}
return this.tx_json;
};
Transaction.prototype.serialize = function () {
@@ -250,14 +252,22 @@ Transaction.prototype._hasTransactionListeners = 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(){};
function finish(err) {
self.emit('error', err);
self.callback('error', err);
}
if (typeof tx_json.Account !== 'string') {
(this.callback || this.emit)('error', {
finish({
'error' : 'tejInvalidAccount',
'error_message' : 'Bad account.'
});
@@ -268,72 +278,72 @@ Transaction.prototype.submit = function (callback) {
this.complete();
if (this.callback || this._hasTransactionListeners()) {
// 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.
function on_ledger_closed(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.
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;
if (self.callback) {
self.callback(message.metadata.TransactionResult, message);
// When a ledger closes, look for the result.
function on_ledger_closed(message) {
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');
}
});
transaction_entry.on('error', function (message) {
if (self.finalized) return;
}
// XXX Could log other unexpectedness.
});
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');
if (self.callback) {
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();
};
transaction_entry.request();
};
this.remote.on('ledger_closed', on_ledger_closed);
this.remote.on('ledger_closed', on_ledger_closed);
if (this.callback) {
this.once('error', function (message) {
self.callback(message.error, message);
});
}
}
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.
var account_seq = this.remote.account_seq_cache(self.tx_json.Account, false)
@@ -364,8 +374,8 @@ Transaction.prototype.submit = function (callback) {
// 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 && typeof res.engine_result === 'string') {
this.once('success', function (res) {
if (typeof res.engine_result === 'string') {
switch (res.engine_result.slice(0, 3)) {
// Synchronous local error
case 'tej':
@@ -401,7 +411,7 @@ Transaction.prototype.submit = function (callback) {
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."
});
@@ -411,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);