UNPKG

ripplelib

Version:

A JavaScript API for interacting with Ripple in Node.js and the browser

205 lines (174 loc) 5.87 kB
var util = require('util'); var EventEmitter = require('events').EventEmitter; // ================================================================= function Listener (account) { EventEmitter.call(this); var self = this; this._account = account; this._accountID = account._account_id; this._remote = account._remote; this._minLedger = null; this._marker = null; this._memoFilter = ['Payment']; function handleLedgerClosed (ledger) { if (ledger.ledger_index <= self._minLedger) return; self._minLedger = ledger.ledger_index; self.emit('ledger_closed', ledger); } function handleReconnect() { self._handleReconnect(function (err) { // Handle reconnect, account_tx procedure first, before // hooking back into ledger_closed self._remote.on('ledger_closed', handleLedgerClosed); self.emit('reconnected', err); }); } this._remote.on('ledger_closed', handleLedgerClosed) this._remote.on('disconnect', function () { self._remote.removeListener('ledger_closed', handleLedgerClosed); self._remote.once('connect', handleReconnect); }); this._account.on('transaction', function(res) { self._transactionReceived(res); }); } util.inherits(Listener, EventEmitter); /** * On reconnect, load account_tx to capture missed tx while disconnected * * @param [Function] callback * @api private */ Listener.prototype._handleReconnect = function (callback){ if (typeof callback != 'function') callback = function(){}; var self = this; var minLedger = this._minLedger; var options = { account: this._accountID, ledger_index_min: minLedger || -1, ledger_index_max: -1, binary: true, parseBinary: true, limit: 20, forward: (minLedger > 0) ? true : false }; function handleTransactions(err, transactions) { if (err || typeof transactions !== 'object') { callback(true); return; } if (Array.isArray(transactions.transactions)) { // sort the transactions in forward order if (! options.forward) { transactions.transactions.sort(function(a,b){ return a.tx.Sequence - b.tx.Sequence; }) } // Treat each transaction in account transaction history as received transactions.transactions.forEach(self._transactionReceived, self); } if (options.forward && transactions.marker) { options.marker = transactions.marker; self._remote.requestAccountTx(options, handleTransactions); } else { callback(); } } this._remote.requestAccountTx(options, handleTransactions); } /** * Handle received transaction * * @param {Object} transaction * @api private */ Listener.prototype._transactionReceived = function (tx) { var transaction = Listener.normalizeTransaction(tx); var self = this; if (!transaction.validated) return; // filter out txns older than last-received. var marker = Listener.markerString(transaction); if (marker < this._marker) return; var account = transaction.tx_json.Account; var type = transaction.tx_json.TransactionType; this.emit('transaction', transaction); this.emit(account == this._accountID ? 'transaction-outbound' : 'transaction-inbound', transaction); if (type == 'Payment') { this.emit('payment', transaction); this.emit(account == this._accountID ? 'payment-out' : 'payment-in', transaction); } if (this._memoFilter.indexOf(type) >= 0 && transaction.tx_json.Memos && transaction.tx_json.Memos.length) { var hasMemo = false transaction.tx_json.Memos.forEach(function (m){ //only bother when memodata present if (m.Memo.MemoData) hasMemo = true; }); if (! hasMemo) return; this.emit('memo', transaction); this.emit(account == this._accountID ? 'memo-out' : 'memo-in', transaction); } this._marker = marker; this._minLedger = transaction.ledger_index; } Listener.markerString = function (tx) { function toHex (num) { return ("00000000" + num.toString(16)).substr(-8); } return toHex(tx.ledger_index) + toHex(tx.metadata.TransactionIndex); } /** * Normalize transactions received from * account transaction stream and account_tx * * @param {Transaction} * @return {Transaction} normalized * @api private */ Listener.normalizeTransaction = function(tx) { var transaction = { }; var keys = Object.keys(tx); for (var i = 0; i < keys.length; i++) { var k = keys[i]; switch (k) { case 'transaction': // Account transaction stream transaction.tx_json = tx[k]; break; case 'tx': // account_tx response transaction.engine_result = tx.meta.TransactionResult; transaction.result = transaction.engine_result; transaction.tx_json = tx[k]; transaction.hash = tx[k].hash; transaction.ledger_index = tx[k].ledger_index; transaction.type = 'transaction'; transaction.validated = tx.validated; break; case 'meta': case 'metadata': transaction.metadata = tx[k]; break; case 'mmeta': // Don't copy mmeta break; default: transaction[k] = tx[k]; } } if (transaction.engine_result == 'tesSUCCESS' && transaction.metadata) { switch (typeof transaction.metadata.DeliveredAmount) { case 'string': case 'object': transaction.metadata.delivered_amount = transaction.metadata.DeliveredAmount; break; default: switch (typeof transaction.tx_json.Amount) { case 'string': case 'object': transaction.metadata.delivered_amount = transaction.tx_json.Amount; break; } } } transaction.normalized = true; return transaction; }; module.exports = Listener;