mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-28 08:05:51 +00:00
BREAKING CHANGE: "servers" parameter changed to single "server" and added a new broadcast wrapper class for multiple servers
This commit is contained in:
@@ -112,7 +112,7 @@ authorization | string | *Optional* Username and password for HTTP basic authent
|
||||
feeCushion | number | *Optional* Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`.
|
||||
proxy | uri string | *Optional* URI for HTTP/HTTPS proxy to use to connect to the rippled server.
|
||||
proxyAuthorization | string | *Optional* Username and password for HTTP basic authentication to the proxy in the format **username:password**.
|
||||
servers | array\<uri string\> | *Optional* Array of rippled servers to connect to. Currently only one server is supported.
|
||||
server | uri string | *Optional* URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.
|
||||
timeout | integer | *Optional* Timeout in milliseconds before considering a request to have failed.
|
||||
trace | boolean | *Optional* If true, log rippled requests and responses to stdout.
|
||||
trustedCertificates | array\<string\> | *Optional* Array of PEM-formatted SSL certificates to trust when connecting to a proxy. This is useful if you want to use a self-signed certificate on the proxy server. Note: Each element must contain a single certificate; concatenated certificates are not valid.
|
||||
|
||||
144
src/api.js
Normal file
144
src/api.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
// Enable core-js polyfills. This allows use of ES6/7 extensions listed here:
|
||||
// https://github.com/zloirock/core-js/blob/fb0890f32dabe8d4d88a4350d1b268446127132e/shim.js#L1-L103
|
||||
/* eslint-enable max-len */
|
||||
|
||||
// In node.js env, polyfill might be already loaded (from any npm package),
|
||||
// that's why we do this check.
|
||||
if (!global._babelPolyfill) {
|
||||
require('babel-core/polyfill');
|
||||
}
|
||||
|
||||
const _ = require('lodash');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const common = require('./common');
|
||||
const server = require('./server/server');
|
||||
const connect = server.connect;
|
||||
const disconnect = server.disconnect;
|
||||
const getServerInfo = server.getServerInfo;
|
||||
const getFee = server.getFee;
|
||||
const isConnected = server.isConnected;
|
||||
const getLedgerVersion = server.getLedgerVersion;
|
||||
const getTransaction = require('./ledger/transaction');
|
||||
const getTransactions = require('./ledger/transactions');
|
||||
const getTrustlines = require('./ledger/trustlines');
|
||||
const getBalances = require('./ledger/balances');
|
||||
const getBalanceSheet = require('./ledger/balance-sheet');
|
||||
const getPaths = require('./ledger/pathfind');
|
||||
const getOrders = require('./ledger/orders');
|
||||
const getOrderbook = require('./ledger/orderbook');
|
||||
const getSettings = require('./ledger/settings');
|
||||
const getAccountInfo = require('./ledger/accountinfo');
|
||||
const preparePayment = require('./transaction/payment');
|
||||
const prepareTrustline = require('./transaction/trustline');
|
||||
const prepareOrder = require('./transaction/order');
|
||||
const prepareOrderCancellation = require('./transaction/ordercancellation');
|
||||
const prepareSuspendedPaymentCreation =
|
||||
require('./transaction/suspended-payment-creation');
|
||||
const prepareSuspendedPaymentExecution =
|
||||
require('./transaction/suspended-payment-execution');
|
||||
const prepareSuspendedPaymentCancellation =
|
||||
require('./transaction/suspended-payment-cancellation');
|
||||
const prepareSettings = require('./transaction/settings');
|
||||
const sign = require('./transaction/sign');
|
||||
const submit = require('./transaction/submit');
|
||||
const errors = require('./common').errors;
|
||||
const generateAddress = common.generateAddressAPI;
|
||||
const computeLedgerHash = require('./offline/ledgerhash');
|
||||
const getLedger = require('./ledger/ledger');
|
||||
|
||||
type APIOptions = {
|
||||
server?: string,
|
||||
feeCushion?: number,
|
||||
trace?: boolean,
|
||||
proxy?: string,
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
// prevent access to non-validated ledger versions
|
||||
class RestrictedConnection extends common.Connection {
|
||||
request(request, timeout) {
|
||||
const ledger_index = request.ledger_index;
|
||||
if (ledger_index !== undefined && ledger_index !== 'validated') {
|
||||
if (!_.isNumber(ledger_index) || ledger_index > this._ledgerVersion) {
|
||||
return Promise.reject(new errors.LedgerVersionError(
|
||||
`ledgerVersion ${ledger_index} is greater than server\'s ` +
|
||||
`most recent validated ledger: ${this._ledgerVersion}`));
|
||||
}
|
||||
}
|
||||
return super.request(request, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
class RippleAPI extends EventEmitter {
|
||||
constructor(options: APIOptions = {}) {
|
||||
common.validate.apiOptions(options);
|
||||
super();
|
||||
this._feeCushion = options.feeCushion || 1.2;
|
||||
const serverURL = options.server;
|
||||
if (serverURL !== undefined) {
|
||||
this.connection = new RestrictedConnection(serverURL, options);
|
||||
this.connection.on('ledgerClosed', message => {
|
||||
this.emit('ledger', server.formatLedgerClose(message));
|
||||
});
|
||||
this.connection.on('error', (type, info) => {
|
||||
this.emit('error', type, info);
|
||||
});
|
||||
} else {
|
||||
// use null object pattern to provide better error message if user
|
||||
// tries to call a method that requires a connection
|
||||
this.connection = new RestrictedConnection(null, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_.assign(RippleAPI.prototype, {
|
||||
connect,
|
||||
disconnect,
|
||||
isConnected,
|
||||
getServerInfo,
|
||||
getFee,
|
||||
getLedgerVersion,
|
||||
|
||||
getTransaction,
|
||||
getTransactions,
|
||||
getTrustlines,
|
||||
getBalances,
|
||||
getBalanceSheet,
|
||||
getPaths,
|
||||
getOrders,
|
||||
getOrderbook,
|
||||
getSettings,
|
||||
getAccountInfo,
|
||||
getLedger,
|
||||
|
||||
preparePayment,
|
||||
prepareTrustline,
|
||||
prepareOrder,
|
||||
prepareOrderCancellation,
|
||||
prepareSuspendedPaymentCreation,
|
||||
prepareSuspendedPaymentExecution,
|
||||
prepareSuspendedPaymentCancellation,
|
||||
prepareSettings,
|
||||
sign,
|
||||
submit,
|
||||
|
||||
generateAddress,
|
||||
computeLedgerHash,
|
||||
errors
|
||||
});
|
||||
|
||||
// these are exposed only for use by unit tests; they are not part of the API
|
||||
RippleAPI._PRIVATE = {
|
||||
validate: common.validate,
|
||||
RangeSet: require('./common/rangeset').RangeSet,
|
||||
ledgerUtils: require('./ledger/utils'),
|
||||
schemaValidator: require('./common/schema-validator')
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
RippleAPI
|
||||
};
|
||||
66
src/broadcast.js
Normal file
66
src/broadcast.js
Normal file
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const RippleAPI = require('./api').RippleAPI;
|
||||
|
||||
class RippleAPIBroadcast extends RippleAPI {
|
||||
constructor(servers, options) {
|
||||
super(options);
|
||||
this.ledgerVersion = 0;
|
||||
|
||||
const apis = servers.map(server => new RippleAPI(
|
||||
_.assign({}, options, {server})
|
||||
));
|
||||
|
||||
this.getMethodNames().forEach(name => {
|
||||
this[name] = function() { // eslint-disable-line no-loop-func
|
||||
return Promise.race(apis.map(api => api[name].apply(api, arguments)));
|
||||
};
|
||||
});
|
||||
|
||||
// connection methods must be overridden to apply to all api instances
|
||||
this.connect = function() {
|
||||
return Promise.all(apis.map(api => api.connect()));
|
||||
};
|
||||
this.disconnect = function() {
|
||||
return Promise.all(apis.map(api => api.disconnect()));
|
||||
};
|
||||
this.isConnected = function() {
|
||||
return _.every(apis.map(api => api.isConnected()));
|
||||
};
|
||||
|
||||
// synchronous methods are all passed directly to the first api instance
|
||||
const defaultAPI = apis[0];
|
||||
const syncMethods = ['sign', 'generateAddress', 'computeLedgerHash'];
|
||||
syncMethods.forEach(name => {
|
||||
this[name] = defaultAPI[name].bind(defaultAPI);
|
||||
});
|
||||
|
||||
apis.forEach(api => {
|
||||
api.on('ledger', this.onLedgerEvent.bind(this));
|
||||
api.on('error', (type, info) => this.emit('error', type, info));
|
||||
});
|
||||
}
|
||||
|
||||
onLedgerEvent(ledger) {
|
||||
if (ledger.ledgerVersion > this.ledgerVersion) {
|
||||
this.ledgerVersion = ledger.ledgerVersion;
|
||||
this.emit('ledger', ledger);
|
||||
}
|
||||
}
|
||||
|
||||
getMethodNames() {
|
||||
const methodNames = [];
|
||||
for (const name in RippleAPI.prototype) {
|
||||
if (RippleAPI.prototype.hasOwnProperty(name)) {
|
||||
if (typeof RippleAPI.prototype[name] === 'function') {
|
||||
methodNames.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodNames;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RippleAPIBroadcast
|
||||
};
|
||||
@@ -12,15 +12,11 @@
|
||||
"minimum": 1,
|
||||
"description": "Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`."
|
||||
},
|
||||
"servers": {
|
||||
"type": "array",
|
||||
"description": "Array of rippled servers to connect to. Currently only one server is supported.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "URI for rippled websocket port. Must start with `wss://` or `ws://`.",
|
||||
"format": "uri",
|
||||
"pattern": "^wss?://"
|
||||
}
|
||||
"server": {
|
||||
"type": "string",
|
||||
"description": "URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.",
|
||||
"format": "uri",
|
||||
"pattern": "^wss?://"
|
||||
},
|
||||
"proxy": {
|
||||
"format": "uri",
|
||||
|
||||
146
src/index.js
146
src/index.js
@@ -1,146 +1,6 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
// Enable core-js polyfills. This allows use of ES6/7 extensions listed here:
|
||||
// https://github.com/zloirock/core-js/blob/fb0890f32dabe8d4d88a4350d1b268446127132e/shim.js#L1-L103
|
||||
/* eslint-enable max-len */
|
||||
|
||||
// In node.js env, polyfill might be already loaded (from any npm package),
|
||||
// that's why we do this check.
|
||||
if (!global._babelPolyfill) {
|
||||
require('babel-core/polyfill');
|
||||
}
|
||||
|
||||
const _ = require('lodash');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const common = require('./common');
|
||||
const server = require('./server/server');
|
||||
const connect = server.connect;
|
||||
const disconnect = server.disconnect;
|
||||
const getServerInfo = server.getServerInfo;
|
||||
const getFee = server.getFee;
|
||||
const isConnected = server.isConnected;
|
||||
const getLedgerVersion = server.getLedgerVersion;
|
||||
const getTransaction = require('./ledger/transaction');
|
||||
const getTransactions = require('./ledger/transactions');
|
||||
const getTrustlines = require('./ledger/trustlines');
|
||||
const getBalances = require('./ledger/balances');
|
||||
const getBalanceSheet = require('./ledger/balance-sheet');
|
||||
const getPaths = require('./ledger/pathfind');
|
||||
const getOrders = require('./ledger/orders');
|
||||
const getOrderbook = require('./ledger/orderbook');
|
||||
const getSettings = require('./ledger/settings');
|
||||
const getAccountInfo = require('./ledger/accountinfo');
|
||||
const preparePayment = require('./transaction/payment');
|
||||
const prepareTrustline = require('./transaction/trustline');
|
||||
const prepareOrder = require('./transaction/order');
|
||||
const prepareOrderCancellation = require('./transaction/ordercancellation');
|
||||
const prepareSuspendedPaymentCreation =
|
||||
require('./transaction/suspended-payment-creation');
|
||||
const prepareSuspendedPaymentExecution =
|
||||
require('./transaction/suspended-payment-execution');
|
||||
const prepareSuspendedPaymentCancellation =
|
||||
require('./transaction/suspended-payment-cancellation');
|
||||
const prepareSettings = require('./transaction/settings');
|
||||
const sign = require('./transaction/sign');
|
||||
const submit = require('./transaction/submit');
|
||||
const errors = require('./common').errors;
|
||||
const generateAddress = common.generateAddressAPI;
|
||||
const computeLedgerHash = require('./offline/ledgerhash');
|
||||
const getLedger = require('./ledger/ledger');
|
||||
|
||||
type APIOptions = {
|
||||
servers?: Array<string>,
|
||||
feeCushion?: number,
|
||||
trace?: boolean,
|
||||
proxy?: string,
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
// prevent access to non-validated ledger versions
|
||||
class RestrictedConnection extends common.Connection {
|
||||
request(request, timeout) {
|
||||
const ledger_index = request.ledger_index;
|
||||
if (ledger_index !== undefined && ledger_index !== 'validated') {
|
||||
if (!_.isNumber(ledger_index) || ledger_index > this._ledgerVersion) {
|
||||
return Promise.reject(new errors.LedgerVersionError(
|
||||
`ledgerVersion ${ledger_index} is greater than server\'s ` +
|
||||
`most recent validated ledger: ${this._ledgerVersion}`));
|
||||
}
|
||||
}
|
||||
return super.request(request, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
class RippleAPI extends EventEmitter {
|
||||
constructor(options: APIOptions = {}) {
|
||||
common.validate.apiOptions(options);
|
||||
super();
|
||||
this._feeCushion = options.feeCushion || 1.2;
|
||||
if (options.servers !== undefined) {
|
||||
const servers: Array<string> = options.servers;
|
||||
if (servers.length === 1) {
|
||||
this.connection = new RestrictedConnection(servers[0], options);
|
||||
this.connection.on('ledgerClosed', message => {
|
||||
this.emit('ledger', server.formatLedgerClose(message));
|
||||
});
|
||||
this.connection.on('error', (type, info) => {
|
||||
this.emit('error', type, info);
|
||||
});
|
||||
} else {
|
||||
throw new errors.RippleError('Multi-server not implemented');
|
||||
}
|
||||
} else {
|
||||
// use null object pattern to provide better error message if user
|
||||
// tries to call a method that requires a connection
|
||||
this.connection = new RestrictedConnection(null, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_.assign(RippleAPI.prototype, {
|
||||
connect,
|
||||
disconnect,
|
||||
isConnected,
|
||||
getServerInfo,
|
||||
getFee,
|
||||
getLedgerVersion,
|
||||
|
||||
getTransaction,
|
||||
getTransactions,
|
||||
getTrustlines,
|
||||
getBalances,
|
||||
getBalanceSheet,
|
||||
getPaths,
|
||||
getOrders,
|
||||
getOrderbook,
|
||||
getSettings,
|
||||
getAccountInfo,
|
||||
getLedger,
|
||||
|
||||
preparePayment,
|
||||
prepareTrustline,
|
||||
prepareOrder,
|
||||
prepareOrderCancellation,
|
||||
prepareSuspendedPaymentCreation,
|
||||
prepareSuspendedPaymentExecution,
|
||||
prepareSuspendedPaymentCancellation,
|
||||
prepareSettings,
|
||||
sign,
|
||||
submit,
|
||||
|
||||
generateAddress,
|
||||
computeLedgerHash,
|
||||
errors
|
||||
});
|
||||
|
||||
// these are exposed only for use by unit tests; they are not part of the API
|
||||
RippleAPI._PRIVATE = {
|
||||
validate: common.validate,
|
||||
RangeSet: require('./common/rangeset').RangeSet,
|
||||
ledgerUtils: require('./ledger/utils'),
|
||||
schemaValidator: require('./common/schema-validator')
|
||||
module.exports = {
|
||||
RippleAPI: require('./api').RippleAPI,
|
||||
RippleAPIBroadcast: require('./broadcast').RippleAPIBroadcast
|
||||
};
|
||||
|
||||
module.exports.RippleAPI = RippleAPI;
|
||||
|
||||
@@ -79,8 +79,8 @@ describe('RippleAPI', function() {
|
||||
}, /XRP to XRP payments cannot be partial payments/);
|
||||
});
|
||||
|
||||
it('preparePayment - address must match payment.source.address',
|
||||
function() {
|
||||
it('preparePayment - address must match payment.source.address', function(
|
||||
) {
|
||||
assert.throws(() => {
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongAddress);
|
||||
}, /address must match payment.source.address/);
|
||||
@@ -1300,7 +1300,7 @@ describe('RippleAPI - offline', function() {
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
it('RippleAPI - implicit server port', function() {
|
||||
const api = new RippleAPI({servers: ['wss://s1.ripple.com']});
|
||||
const api = new RippleAPI({server: 'wss://s1.ripple.com'});
|
||||
});
|
||||
/* eslint-enable no-unused-vars */
|
||||
it('RippleAPI invalid options', function() {
|
||||
@@ -1308,12 +1308,12 @@ describe('RippleAPI - offline', function() {
|
||||
});
|
||||
|
||||
it('RippleAPI valid options', function() {
|
||||
const api = new RippleAPI({servers: ['wss://s:1']});
|
||||
const api = new RippleAPI({server: 'wss://s:1'});
|
||||
assert.deepEqual(api.connection._url, 'wss://s:1');
|
||||
});
|
||||
|
||||
it('RippleAPI invalid server uri', function() {
|
||||
assert.throws(() => new RippleAPI({servers: ['wss//s:1']}));
|
||||
assert.throws(() => new RippleAPI({server: 'wss//s:1'}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
17
test/integration/broadcast-test.js
Normal file
17
test/integration/broadcast-test.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
const {RippleAPIBroadcast} = require('../../src');
|
||||
|
||||
function main() {
|
||||
const servers = ['wss://s1.ripple.com', 'wss://s2.ripple.com'];
|
||||
const api = new RippleAPIBroadcast(servers);
|
||||
api.connect().then(() => {
|
||||
api.getServerInfo().then(info => {
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
});
|
||||
api.on('ledger', ledger => {
|
||||
console.log(JSON.stringify(ledger, null, 2));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -58,7 +58,7 @@ function testTransaction(testcase, type, lastClosedLedgerVersion, prepared) {
|
||||
}
|
||||
|
||||
function setup() {
|
||||
this.api = new RippleAPI({servers: ['wss://s1.ripple.com']});
|
||||
this.api = new RippleAPI({server: 'wss://s1.ripple.com'});
|
||||
console.log('CONNECTING...');
|
||||
return this.api.connect().then(() => {
|
||||
console.log('CONNECTED...');
|
||||
|
||||
@@ -23,7 +23,7 @@ function getFreePort(callback) {
|
||||
|
||||
function setupMockRippledConnection(testcase, port, done) {
|
||||
testcase.mockRippled = createMockRippled(port);
|
||||
testcase.api = new RippleAPI({servers: ['ws://localhost:' + port]});
|
||||
testcase.api = new RippleAPI({server: 'ws://localhost:' + port});
|
||||
testcase.api.connect().then(() => {
|
||||
testcase.api.once('ledger', () => done());
|
||||
testcase.api.connection._ws.emit('message', JSON.stringify(ledgerClosed));
|
||||
|
||||
Reference in New Issue
Block a user