From f99b2274f9111a40c7692af61d16fe4a2435c739 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Fri, 19 Jun 2015 18:16:28 +0200 Subject: [PATCH 1/2] [FIX] esnext the account monitor --- package.json | 2 +- src/ripple_account_monitor.js | 254 ++++++++++++++++++++-------------- test/monitor_forever.js | 82 ++++++----- 3 files changed, 193 insertions(+), 145 deletions(-) diff --git a/package.json b/package.json index 6592bc7..af6a674 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "author": "Steven Zeiler", "license": "ISC", "dependencies": { - "ripple-rest-client": "^1.13.0" + "ripple-rest-client": "^1.16.1" } } diff --git a/src/ripple_account_monitor.js b/src/ripple_account_monitor.js index 0352f50..a7b11df 100644 --- a/src/ripple_account_monitor.js +++ b/src/ripple_account_monitor.js @@ -1,138 +1,178 @@ -const RippleRestClient = require('ripple-rest-client'); -const ArgumentError = require('./errors/argument_error.js'); +import RippleRestClient from 'ripple-rest-client' +import ArgumentError from './errors/argument_error' -function RippleAccountMonitor(options) { - if (!options) { - throw new ArgumentError('options must be an object'); - } - if (!options.rippleRestUrl) { - throw new ArgumentError('options.rippleRestUrl must be a url'); - } - if (!options.account) { - throw new ArgumentError('options.account must be a ripple Account'); - } - if (typeof options.onTransaction != 'function') { - throw new ArgumentError('options.onTransaction(transaction, next) must be a function'); +export default class RippleAccountMonitor { + + constructor(options) { + if (!options) { + throw new ArgumentError('options must be an object'); + } + + const { + rippleRestUrl, + account, + lastHash, + timeout + } = options + + if (!rippleRestUrl) { + throw new ArgumentError('options.rippleRestUrl must be a url'); + } + + if (!account) { + throw new ArgumentError('options.account must be a ripple Account'); + } + + this.rippleRestClient = new RippleRestClient({ + api: rippleRestUrl, + account: account, + secret: '' + }) + + this.lastHash = lastHash + this.timeout = timeout || 5000 + this.stopped = true } - this.rippleRestClient = new RippleRestClient({ - api: options.rippleRestUrl, - account: options.account, - secret: '' - }); - this.lastHash = options.lastHash; - this.timeout = options.timeout || 5000; - this.onTransaction = options.onTransaction; - this.onPayment = options.onPayment; - this.onTrustSet = options.onTrustSet; - this.onAccountSet = options.onAccountSet; - this.onOfferCreate = options.onOfferCreate; - this.onOfferCancel = options.onOfferCancel; - this.onSetRegularKey = options.onSetRegularKey; - this.onError = options.onError || function(error) { - console.log('RippleAccountMonitor::Error', error); - }; -} -RippleAccountMonitor.prototype = { + onError() { + console.log('RippleAccountMonitor::Error', error); + console.log('RippleAccountMonitor::Error', error.message); + console.log('RippleAccountMonitor::Error', error.stack); + } - start: function() { - var _this = this; - if (_this.lastHash) { - _this._processNextTransaction(); + start() { + this.stopped = false + if (this.lastHash) { + this._processNextTransaction(); } else { + console.error('no transaction specified') + process.exit(1) // get the most recent hash, then: // _this._processNextTransaction(); } - }, + } + + stop() { + this.stopped = true + } + + _loop(timeout) { + if (this.stopped) { + return + } - stop: function() { - }, - - _loop: function(timeout) { - var _this = this; + let process = this._processNextTransaction.bind(this) if (timeout) { - setTimeout(_this._processNextTransaction.bind(_this), timeout); + setTimeout(process, timeout); } else { - setImmediate(_this._processNextTransaction.bind(_this)); + setImmediate(process); } - }, - - _getNextTransaction: function(callback) { - var _this = this; - _this.rippleRestClient.getNotification(_this.lastHash, function(error, notification) { - if (error) { - _this.onError(error); - return callback(error); - } - if (!notification) { - return callback(); - } - _this.rippleRestClient.getNotification(_this.lastHash, function(error, notification) { - if (error) { - _this.onError(error); - return callback(error); - } - if (!notification) { - return callback(); - } - if (notification.next_notification_hash) { - return _this.rippleRestClient.getTransaction(notification.next_notification_hash, function(error, response) { - if (error) { - _this.onError(error); - return callback(error); - } - if (!response) { - callback(); - } else { - callback(null, response.transaction); - } - }); - } else { - return callback(); - } - }); - }); - }, - - _processNextTransaction: function() { - var _this = this; - _this._getNextTransaction(function(error, transaction) { - if (error) { - _this.onError(error); - return _this._loop(_this.timeout); - } - if (!transaction) { - return _this._loop(_this.timeout); + } + + _getNotificationAsync(hash) { + if (!hash) { + throw new Error('no hash') + } + if (!this.__getNotificationAsync) { + this.__getNotificationAsync = promisify(this.rippleRestClient.getNotification.bind(this.rippleRestClient)) + } + + return this.__getNotificationAsync(hash) + } + + _getTransactionAsync(hash) { + if (!hash) { + throw new Error('no hash') + } + if (!this.__getTransactionAsync) { + this.__getTransactionAsync = promisify(this.rippleRestClient.getTransaction.bind(this.rippleRestClient)) + } + + return this.__getTransactionAsync(hash) + } + + async _getNextTransaction() { + const notification = await this._getNotificationAsync(this.lastHash) + if (!notification) { + throw new Error('no notification') + } + + if (notification.next_notification_hash) { + const response = await this._getTransactionAsync(notification.next_notification_hash) + + if (!response.transaction) { + throw new Error('no transaction') } - var hook = _this.onTransaction; - switch(transaction.TransactionType) { + + return response.transaction + } + + else { + // this return is "caught" on line 142 + return + } + } + + async _processNextTransaction() { + try { + const transaction = await this._getNextTransaction() + + // if not transaction, loop until there is one + if (transaction) { + + let hook + switch (transaction.TransactionType) { case 'Payment': - if (typeof _this.onPayment === 'function') { hook = _this.onPayment } + if (typeof this.onPayment === 'function') { hook = this.onPayment } break; case 'TrustSet': - if (typeof _this.onTrustSet === 'function') { hook = _this.onTrustSet } + if (typeof this.onTrustSet === 'function') { hook = this.onTrustSet } break; case 'AccountSet': - if (typeof _this.onAccountSet === 'function') { hook = _this.onAccountSet } + if (typeof this.onAccountSet === 'function') { hook = this.onAccountSet } break; case 'OfferCreate': - if (typeof _this.onOfferCreate === 'function') { hook = _this.onOfferCreate } + if (typeof this.onOfferCreate === 'function') { hook = this.onOfferCreate } break; case 'OfferCancel': - if (typeof _this.onOfferCancel === 'function') { hook = _this.onOfferCancel } + if (typeof this.onOfferCancel === 'function') { hook = this.onOfferCancel } break; case 'SetRegularKey': - if (typeof _this.onSetRegularKey === 'function') { hook = _this.onSetRegularKey } + if (typeof this.onSetRegularKey === 'function') { hook = this.onSetRegularKey } break; default: + hook = this.onTransaction + } + + const result = hook.call(this, transaction) + + // if hook asynchronous, wait for completion + if (result instanceof Promise) { + await result + } + // else hook was not asynchronous, can continue + + this.lastHash = transaction.hash + } - hook(transaction, function() { - _this.lastHash = transaction.hash; - _this._loop(); - }); - }.bind(_this)); + } + catch (error) { + this.onError(error) + } + + this._loop(this.timeout) } } -module.exports = RippleAccountMonitor; +// helper function, see bluebird.promisify +// does not bind context, do this yourself +function promisify(boundMethod) { + return function(param) { + return new Promise((resolve, reject) => { + boundMethod(param, (error, result) => { + error? reject(error) : resolve(result) + }) + }) + } +} diff --git a/test/monitor_forever.js b/test/monitor_forever.js index dce78ec..bafaa3a 100644 --- a/test/monitor_forever.js +++ b/test/monitor_forever.js @@ -1,42 +1,50 @@ -const RippleAccountMonitor = require('../src/ripple_account_monitor.js'); +import RippleAccountMonitor from '../src/ripple_account_monitor' -const monitor = new RippleAccountMonitor({ - rippleRestUrl: 'https://api.ripple.com/', +class Monitor extends RippleAccountMonitor { + onTransaction({TransactionType}, next) { + console.log('new transaction', TransactionType) + } + + onPayment({hash}) { + console.log('new payment', hash) + } + + onTrustSet({hash}) { + console.log('new trust set', hash) + } + + onAccountSet({hash}) { + console.log('new account setting', transaction.hash) + } + + onOfferCreate(transaction, next) { + console.log('new offer created', transaction.hash) + next() + } + + onOfferCancel(transaction, next) { + console.log('offer cancelled', transaction.hash) + next() + } + + onSetRegularKey(transaction, next) { + console.log('regular key set', transaction.hash) + next() + } + + onError(error) { + console.log('RippleAccountMonitor::Error', error, + 'RippleAccountMonitor::Error', error.message, + 'RippleAccountMonitor::Error', error.stack) + } +} + +const monitor = new Monitor({ + rippleRestUrl: 'http://127.0.0.1:5990/', account: 'r4EwBWxrx5HxYRyisfGzMto3AT8FZiYdWk', - lastHash: 'EF5D38031A961C32D4170A1E7A888D57F553D36F40796C94D27C2497F6722E62', - timeout: 1000, - onTransaction: function(transaction, next) { - console.log('new transaction', transaction.TransactionType); - next(); - }, - onPayment: function(transaction, next) { - console.log('new payment', transaction.hash); - next(); - }, - onTrustSet: function(transaction, next) { - console.log('new trust set', transaction.hash); - next(); - }, - onAccountSet: function(transaction, next) { - console.log('new account setting', transaction.hash); - next(); - }, - onOfferCreate: function(transaction, next) { - console.log('new offer created', transaction.hash); - next(); - }, - onOfferCancel: function(transaction, next) { - console.log('offer cancelled', transaction.hash); - next(); - }, - onSetRegularKey: function(transaction, next) { - console.log('regular key set', transaction.hash); - next(); - }, - onError: function(error) { - console.log('RippleAccountMonitor::Error', error); - } -}); + lastHash: '07814E2CAF5677F4D513F1C49849F5974CCD9AFD725F65DEC30FB130CD59A3A9', + timeout: 1000 +}) monitor.start(); From 6f4f84fe44c8765a04b85ca0b4e6f908548ed738 Mon Sep 17 00:00:00 2001 From: Adam Cmiel Date: Fri, 19 Jun 2015 18:28:06 +0200 Subject: [PATCH 2/2] [FIX] build project for esbehind --- .babelrc | 5 + build/errors/argument_error.js | 10 ++ build/ripple_account_monitor.js | 290 ++++++++++++++++++++++++++++++++ package.json | 8 +- src/errors/argument_error.js | 11 +- 5 files changed, 315 insertions(+), 9 deletions(-) create mode 100644 .babelrc create mode 100644 build/errors/argument_error.js create mode 100644 build/ripple_account_monitor.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..3390a4b --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "optional": [ + "es7.asyncFunctions" + ] +} diff --git a/build/errors/argument_error.js b/build/errors/argument_error.js new file mode 100644 index 0000000..79a0eba --- /dev/null +++ b/build/errors/argument_error.js @@ -0,0 +1,10 @@ +'use strict'; + +function ArgumentError(message) { + this.name = 'ArgumentError'; + this.message = message || ''; +} + +ArgumentError.prototype = Error.prototype; + +module.exports = ArgumentError; \ No newline at end of file diff --git a/build/ripple_account_monitor.js b/build/ripple_account_monitor.js new file mode 100644 index 0000000..d6088dc --- /dev/null +++ b/build/ripple_account_monitor.js @@ -0,0 +1,290 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +var _rippleRestClient = require('ripple-rest-client'); + +var _rippleRestClient2 = _interopRequireDefault(_rippleRestClient); + +var _errorsArgument_error = require('./errors/argument_error'); + +var _errorsArgument_error2 = _interopRequireDefault(_errorsArgument_error); + +var RippleAccountMonitor = (function () { + function RippleAccountMonitor(options) { + _classCallCheck(this, RippleAccountMonitor); + + if (!options) { + throw new _errorsArgument_error2['default']('options must be an object'); + } + + var rippleRestUrl = options.rippleRestUrl; + var account = options.account; + var lastHash = options.lastHash; + var timeout = options.timeout; + + if (!rippleRestUrl) { + throw new _errorsArgument_error2['default']('options.rippleRestUrl must be a url'); + } + + if (!account) { + throw new _errorsArgument_error2['default']('options.account must be a ripple Account'); + } + + this.rippleRestClient = new _rippleRestClient2['default']({ + api: rippleRestUrl, + account: account, + secret: '' + }); + + this.lastHash = lastHash; + this.timeout = timeout || 5000; + this.stopped = true; + } + + _createClass(RippleAccountMonitor, [{ + key: 'onError', + value: function onError() { + console.log('RippleAccountMonitor::Error', error); + console.log('RippleAccountMonitor::Error', error.message); + console.log('RippleAccountMonitor::Error', error.stack); + } + }, { + key: 'start', + value: function start() { + this.stopped = false; + if (this.lastHash) { + this._processNextTransaction(); + } else { + console.error('no transaction specified'); + process.exit(1); + } + } + }, { + key: 'stop', + value: function stop() { + this.stopped = true; + } + }, { + key: '_loop', + value: function _loop(timeout) { + if (this.stopped) { + return; + } + + var process = this._processNextTransaction.bind(this); + if (timeout) { + setTimeout(process, timeout); + } else { + setImmediate(process); + } + } + }, { + key: '_getNotificationAsync', + value: function _getNotificationAsync(hash) { + if (!hash) { + throw new Error('no hash'); + } + if (!this.__getNotificationAsync) { + this.__getNotificationAsync = promisify(this.rippleRestClient.getNotification.bind(this.rippleRestClient)); + } + + return this.__getNotificationAsync(hash); + } + }, { + key: '_getTransactionAsync', + value: function _getTransactionAsync(hash) { + if (!hash) { + throw new Error('no hash'); + } + if (!this.__getTransactionAsync) { + this.__getTransactionAsync = promisify(this.rippleRestClient.getTransaction.bind(this.rippleRestClient)); + } + + return this.__getTransactionAsync(hash); + } + }, { + key: '_getNextTransaction', + value: function _getNextTransaction() { + var notification, response; + return regeneratorRuntime.async(function _getNextTransaction$(context$2$0) { + while (1) switch (context$2$0.prev = context$2$0.next) { + case 0: + context$2$0.next = 2; + return regeneratorRuntime.awrap(this._getNotificationAsync(this.lastHash)); + + case 2: + notification = context$2$0.sent; + + if (notification) { + context$2$0.next = 5; + break; + } + + throw new Error('no notification'); + + case 5: + if (!notification.next_notification_hash) { + context$2$0.next = 14; + break; + } + + context$2$0.next = 8; + return regeneratorRuntime.awrap(this._getTransactionAsync(notification.next_notification_hash)); + + case 8: + response = context$2$0.sent; + + if (response.transaction) { + context$2$0.next = 11; + break; + } + + throw new Error('no transaction'); + + case 11: + return context$2$0.abrupt('return', response.transaction); + + case 14: + return context$2$0.abrupt('return'); + + case 15: + case 'end': + return context$2$0.stop(); + } + }, null, this); + } + }, { + key: '_processNextTransaction', + value: function _processNextTransaction() { + var transaction, hook, result; + return regeneratorRuntime.async(function _processNextTransaction$(context$2$0) { + while (1) switch (context$2$0.prev = context$2$0.next) { + case 0: + context$2$0.prev = 0; + context$2$0.next = 3; + return regeneratorRuntime.awrap(this._getNextTransaction()); + + case 3: + transaction = context$2$0.sent; + + if (!transaction) { + context$2$0.next = 27; + break; + } + + hook = undefined; + context$2$0.t0 = transaction.TransactionType; + context$2$0.next = context$2$0.t0 === 'Payment' ? 9 : context$2$0.t0 === 'TrustSet' ? 11 : context$2$0.t0 === 'AccountSet' ? 13 : context$2$0.t0 === 'OfferCreate' ? 15 : context$2$0.t0 === 'OfferCancel' ? 17 : context$2$0.t0 === 'SetRegularKey' ? 19 : 21; + break; + + case 9: + if (typeof this.onPayment === 'function') { + hook = this.onPayment; + } + return context$2$0.abrupt('break', 22); + + case 11: + if (typeof this.onTrustSet === 'function') { + hook = this.onTrustSet; + } + return context$2$0.abrupt('break', 22); + + case 13: + if (typeof this.onAccountSet === 'function') { + hook = this.onAccountSet; + } + return context$2$0.abrupt('break', 22); + + case 15: + if (typeof this.onOfferCreate === 'function') { + hook = this.onOfferCreate; + } + return context$2$0.abrupt('break', 22); + + case 17: + if (typeof this.onOfferCancel === 'function') { + hook = this.onOfferCancel; + } + return context$2$0.abrupt('break', 22); + + case 19: + if (typeof this.onSetRegularKey === 'function') { + hook = this.onSetRegularKey; + } + return context$2$0.abrupt('break', 22); + + case 21: + hook = this.onTransaction; + + case 22: + result = hook.call(this, transaction); + + if (!(result instanceof Promise)) { + context$2$0.next = 26; + break; + } + + context$2$0.next = 26; + return regeneratorRuntime.awrap(result); + + case 26: + // else hook was not asynchronous, can continue + + this.lastHash = transaction.hash; + + case 27: + context$2$0.next = 32; + break; + + case 29: + context$2$0.prev = 29; + context$2$0.t1 = context$2$0['catch'](0); + + this.onError(context$2$0.t1); + + case 32: + + this._loop(this.timeout); + + case 33: + case 'end': + return context$2$0.stop(); + } + }, null, this, [[0, 29]]); + } + }]); + + return RippleAccountMonitor; +})(); + +exports['default'] = RippleAccountMonitor; + +// helper function, see bluebird.promisify +// does not bind context, do this yourself +function promisify(boundMethod) { + return function (param) { + return new Promise(function (resolve, reject) { + boundMethod(param, function (error, result) { + error ? reject(error) : resolve(result); + }); + }); + }; +} +module.exports = exports['default']; +// get the most recent hash, then: +// _this._processNextTransaction(); + +// this return is "caught" on line 142 + +// if not transaction, loop until there is one + +// if hook asynchronous, wait for completion \ No newline at end of file diff --git a/package.json b/package.json index af6a674..0e2ab3f 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,17 @@ "name": "ripple-account-monitor", "version": "2.2.0", "description": "process ripple account activity serially", - "main": "src/ripple_account_monitor.js", + "main": "build/ripple_account_monitor.js", "scripts": { - "test": "node test/monitor_forever.js" + "test": "node test/monitor_forever.js", + "build": "babel src --out-dir build" }, "author": "Steven Zeiler", "license": "ISC", "dependencies": { "ripple-rest-client": "^1.16.1" + }, + "devDependencies": { + "babel": "^5.5.8" } } diff --git a/src/errors/argument_error.js b/src/errors/argument_error.js index 3abcb89..f48a0e8 100644 --- a/src/errors/argument_error.js +++ b/src/errors/argument_error.js @@ -1,9 +1,6 @@ -function ArgumentError(message) { - this.name = 'ArgumentError'; - this.message = message || ''; +export default class ArgumentError extends Error { + get name() { + return 'ArgumentError' + } } -ArgumentError.prototype = Error.prototype; - -module.exports = ArgumentError; -