diff --git a/lib/app.js b/lib/app.js index fcaf91a9..fc1c77ba 100644 --- a/lib/app.js +++ b/lib/app.js @@ -3,11 +3,11 @@ const http = require('http') const https = require('https') const express = require('express') const loop = require('reoccur') +const argv = require('minimist')(process.argv.slice(2)) const routes = require('./routes') -const plugins = require('./plugins') const logger = require('./logger') -const argv = require('minimist')(process.argv.slice(2)) +const poller = require('./poller') const settingsLoader = require('./settings-loader') const options = require('./options') @@ -33,9 +33,8 @@ function runOnce () { const localApp = express() return settingsLoader.load() - .then(() => { - plugins.startPolling() - plugins.startCheckingNotification() + .then(settings => { + poller.start(settings) const httpsServerOptions = { key: fs.readFileSync(options.keyPath), @@ -56,8 +55,7 @@ function runOnce () { const opts = { app, localApp, - devMode, - plugins + devMode } routes.init(opts) diff --git a/lib/notifier.js b/lib/notifier.js index 418713b4..6baae2a8 100644 --- a/lib/notifier.js +++ b/lib/notifier.js @@ -6,15 +6,15 @@ const numeral = require('numeral') const configManager = require('./config-manager') const settingsLoader = require('./settings-loader') const db = require('./postgresql_interface') +const T = require('./time') +const logger = require('./logger') -const STALE_STATE = 2 * 60 * 1000 -const NETWORK_DOWN_TIME = 60 * 1000 +const STALE_STATE = 2 * T.minute +const NETWORK_DOWN_TIME = T.minute +const ALERT_SEND_INTERVAL = T.hour -let getBalances - -function init (_getBalances) { - getBalances = _getBalances -} +let alertFingerprint +let lastAlertTime function toInt10 (str) { return parseInt(str, 10) } @@ -26,6 +26,57 @@ function sameState (a, b) { return a.note.txId === b.note.txId && a.note.state === b.note.state } +function sendNoAlerts (plugins) { + const subject = '[Lamassu] All clear' + const rec = { + sms: { + body: subject + }, + email: { + subject, + body: 'No errors are reported for your machines.' + } + } + + return plugins.sendMessage(rec) +} + +function checkNotification (plugins) { + return checkStatus(plugins) + .then(alertRec => { + const currentAlertFingerprint = buildAlertFingerprint(alertRec) + if (!currentAlertFingerprint) { + const inAlert = !!alertFingerprint + alertFingerprint = null + lastAlertTime = null + if (inAlert) return sendNoAlerts(plugins) + } + + const alertChanged = currentAlertFingerprint === alertFingerprint && + lastAlertTime - Date.now() < ALERT_SEND_INTERVAL + if (alertChanged) return + + const subject = alertSubject(alertRec) + const rec = { + sms: { + body: subject + }, + email: { + subject, + body: printEmailAlerts(alertRec) + } + } + alertFingerprint = currentAlertFingerprint + lastAlertTime = Date.now() + + return plugins.sendMessage(rec) + }) + .then(results => { + if (results && results.length > 0) logger.debug('Successfully sent alerts') + }) + .catch(logger.error) +} + function checkBalance (rec) { const settings = settingsLoader.settings() const config = configManager.unscoped(settings.config) @@ -35,8 +86,8 @@ function checkBalance (rec) { : null } -function checkBalances () { - return getBalances() +function checkBalances (plugins) { + return plugins.checkBalances() .then(balances => R.reject(R.isNil, balances.map(checkBalance))) } @@ -85,10 +136,10 @@ function devicesAndEvents () { .then(arr => ({devices: arr[0], events: arr[1]})) } -function checkStatus () { +function checkStatus (plugins) { const alerts = {devices: {}, deviceNames: {}} - return Promise.all([checkBalances(), devicesAndEvents()]) + return Promise.all([checkBalances(plugins), devicesAndEvents()]) .then(([balances, rec]) => { const devices = rec.devices const events = rec.events @@ -163,16 +214,10 @@ function alertSubject (alertRec) { return '[Lamassu] Errors reported: ' + alertTypes.join(', ') } -function alertFingerprint (alertRec) { +function buildAlertFingerprint (alertRec) { const subject = alertSubject(alertRec) if (!subject) return null return crypto.createHash('sha256').update(subject).digest('hex') } -module.exports = { - init, - checkStatus, - printEmailAlerts, - alertFingerprint, - alertSubject -} +module.exports = {checkNotification} diff --git a/lib/plugins.js b/lib/plugins.js index 4e311446..d10afe68 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -7,7 +7,6 @@ const crypto = require('crypto') const dbm = require('./postgresql_interface') const db = require('./db') const logger = require('./logger') -const notifier = require('./notifier') const T = require('./time') const configManager = require('./config-manager') const ticker = require('./ticker') @@ -16,8 +15,6 @@ const exchange = require('./exchange') const sms = require('./sms') const email = require('./email') -const CHECK_NOTIFICATION_INTERVAL = 30 * T.seconds -const ALERT_SEND_INTERVAL = T.hour const STALE_INCOMING_TX_AGE = T.week const STALE_LIVE_INCOMING_TX_AGE = 10 * T.minutes const MAX_NOTIFY_AGE = 2 * T.days @@ -34,9 +31,6 @@ const coins = { ETH: {unitScale: 18} } -let alertFingerprint = null -let lastAlertTime = null - function plugins (settings) { function buildRates (deviceId, tickers) { const config = configManager.machineScoped(deviceId, settings.config) @@ -407,56 +401,6 @@ function plugins (settings) { return Promise.all(promises) } - function sendNoAlerts () { - const subject = '[Lamassu] All clear' - const rec = { - sms: { - body: subject - }, - email: { - subject, - body: 'No errors are reported for your machines.' - } - } - return sendMessage(rec) - } - - function checkNotification () { - return notifier.checkStatus() - .then(alertRec => { - const currentAlertFingerprint = notifier.alertFingerprint(alertRec) - if (!currentAlertFingerprint) { - const inAlert = !!alertFingerprint - alertFingerprint = null - lastAlertTime = null - if (inAlert) return sendNoAlerts() - } - - const alertChanged = currentAlertFingerprint === alertFingerprint && - lastAlertTime - Date.now() < ALERT_SEND_INTERVAL - if (alertChanged) return - - const subject = notifier.alertSubject(alertRec) - const rec = { - sms: { - body: subject - }, - email: { - subject, - body: notifier.printEmailAlerts(alertRec) - } - } - alertFingerprint = currentAlertFingerprint - lastAlertTime = Date.now() - - return sendMessage(rec) - }) - .then(results => { - if (results && results.length > 0) logger.debug('Successfully sent alerts') - }) - .catch(logger.error) - } - function checkDeviceBalances (deviceId) { const config = configManager.machineScoped(deviceId, settings.config) const cryptoCodes = config.cryptoCurrencies @@ -489,12 +433,6 @@ function plugins (settings) { }) } - function startCheckingNotification (config) { - notifier.init(checkBalances) - checkNotification() - setInterval(checkNotification, CHECK_NOTIFICATION_INTERVAL) - } - function randomCode () { return new BigNumber(crypto.randomBytes(3).toString('hex'), 16).shift(-6).toFixed(6).slice(-6) } @@ -564,7 +502,6 @@ function plugins (settings) { sendCoins, cashOut, dispenseAck, - startCheckingNotification, getPhoneCode, fetchPhoneTx, executeTrades, @@ -574,7 +511,9 @@ function plugins (settings) { monitorIncoming, monitorUnnotified, sweepLiveHD, - sweepOldHD + sweepOldHD, + sendMessage, + checkBalances } } diff --git a/lib/poller.js b/lib/poller.js index 477c3d46..71b29ae1 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -1,4 +1,5 @@ const plugins = require('./plugins') +const notifier = require('./notifier') const T = require('./time') const INCOMING_TX_INTERVAL = 30 * T.seconds @@ -9,6 +10,7 @@ const SWEEP_OLD_HD_INTERVAL = 2 * T.minutes const TRADE_INTERVAL = 10 * T.seconds const PONG_INTERVAL = 10 * T.seconds const PONG_CLEAR_INTERVAL = 1 * T.day +const CHECK_NOTIFICATION_INTERVAL = 30 * T.seconds let pi @@ -27,6 +29,7 @@ function start (settings) { pi.monitorUnnotified() pi.sweepLiveHD() pi.sweepOldHD() + notifier.checkNotification(pi) setInterval(() => pi.executeTrades(), TRADE_INTERVAL) setInterval(() => pi.monitorLiveIncoming(), LIVE_INCOMING_TX_INTERVAL) @@ -36,6 +39,7 @@ function start (settings) { setInterval(() => pi.sweepOldHD(), SWEEP_OLD_HD_INTERVAL) setInterval(() => pi.pong(), PONG_INTERVAL) setInterval(() => pi.pongClear(), PONG_CLEAR_INTERVAL) + setInterval(() => notifier.checkNotification(pi), CHECK_NOTIFICATION_INTERVAL) } module.exports = {start, reload} diff --git a/lib/routes.js b/lib/routes.js index 5bfd4aaa..1767188e 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -13,8 +13,7 @@ const db = require('./db') const dbm = require('./postgresql_interface') const pairing = require('./pairing') const settingsLoader = require('./settings-loader') - -let plugins +const plugins = require('./plugins') module.exports = { init @@ -30,12 +29,13 @@ function poll (req, res, next) { const deviceId = req.deviceId const deviceTime = req.deviceTime const pid = req.query.pid - const settings = settingsLoader.settings() + const settings = req.settings const config = configManager.machineScoped(deviceId, settings.config) + const pi = plugins(settings) pids[deviceId] = {pid, ts: Date.now()} - plugins.pollQueries(deviceTime, deviceId, req.query) + pi.pollQueries(deviceTime, deviceId, req.query) .then(results => { const cartridges = results.cartridges @@ -77,24 +77,28 @@ function poll (req, res, next) { function trade (req, res, next) { const tx = req.body + const pi = plugins(req.settings) + tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) - plugins.trade(req.deviceId, tx) + pi.trade(req.deviceId, tx) .then(() => cacheAndRespond(req, res)) .catch(next) } function stateChange (req, res, next) { - plugins.stateChange(req.deviceId, req.deviceTime, req.body) + const pi = plugins(req.settings) + pi.stateChange(req.deviceId, req.deviceTime, req.body) .then(() => cacheAndRespond(req, res)) .catch(next) } function send (req, res, next) { + const pi = plugins(req.settings) const tx = req.body tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) - return plugins.sendCoins(req.deviceId, tx) + return pi.sendCoins(req.deviceId, tx) .then(status => { const body = {txId: status && status.txId} return cacheAndRespond(req, res, body) @@ -103,35 +107,40 @@ function send (req, res, next) { } function cashOut (req, res, next) { + const pi = plugins(req.settings) logger.info({tx: req.body, cmd: 'cashOut'}) const tx = req.body tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) - return plugins.cashOut(req.deviceId, tx) + return pi.cashOut(req.deviceId, tx) .then(cryptoAddress => cacheAndRespond(req, res, {toAddress: cryptoAddress})) .catch(next) } function dispenseAck (req, res, next) { - plugins.dispenseAck(req.deviceId, req.body.tx) + const pi = plugins(req.settings) + pi.dispenseAck(req.deviceId, req.body.tx) .then(() => cacheAndRespond(req, res)) .catch(next) } function deviceEvent (req, res, next) { - plugins.logEvent(req.deviceId, req.body) + const pi = plugins(req.settings) + pi.logEvent(req.deviceId, req.body) .then(() => cacheAndRespond(req, res)) .catch(next) } function verifyUser (req, res, next) { - plugins.verifyUser(req.body) + const pi = plugins(req.settings) + pi.verifyUser(req.body) .then(idResult => cacheAndRespond(req, res, idResult)) .catch(next) } function verifyTx (req, res, next) { - plugins.verifyTransaction(req.body) + const pi = plugins(req.settings) + pi.verifyTransaction(req.body) .then(idResult => cacheAndRespond(req, res, idResult)) .catch(next) } @@ -159,9 +168,10 @@ function pair (req, res, next) { } function phoneCode (req, res, next) { + const pi = plugins(req.settings) const phone = req.body.phone - return plugins.getPhoneCode(phone) + return pi.getPhoneCode(phone) .then(code => cacheAndRespond(req, res, {code})) .catch(err => { if (err.name === 'BadNumberError') throw httpError('Bad number', 410) @@ -180,7 +190,8 @@ function updatePhone (req, res, next) { } function fetchPhoneTx (req, res, next) { - return plugins.fetchPhoneTx(req.query.phone) + const pi = plugins(req.settings) + return pi.fetchPhoneTx(req.query.phone) .then(r => res.json(r)) .catch(next) } @@ -315,8 +326,6 @@ function authorize (req, res, next) { } function init (opts) { - plugins = opts.plugins - const skip = options.logLevel === 'debug' ? () => false : (req, res) => _.includes(req.path, ['/poll', '/state']) && res.statusCode === 200