Feat: move notif center fns to own file on the notifier module
Fix: fix jest test
This commit is contained in:
parent
34f2b84fe2
commit
69d3e4cb9b
6 changed files with 204 additions and 190 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const notifier = require('./notifier')
|
const notifierQueries = require('./notifier/queries')
|
||||||
|
|
||||||
// Get all blacklist rows from the DB "blacklist" table that were manually inserted by the operator
|
// Get all blacklist rows from the DB "blacklist" table that were manually inserted by the operator
|
||||||
const getBlacklist = () => {
|
const getBlacklist = () => {
|
||||||
|
|
@ -15,7 +15,7 @@ const getBlacklist = () => {
|
||||||
// Delete row from blacklist table by crypto code and address
|
// Delete row from blacklist table by crypto code and address
|
||||||
const deleteFromBlacklist = (cryptoCode, address) => {
|
const deleteFromBlacklist = (cryptoCode, address) => {
|
||||||
const sql = `DELETE FROM blacklist WHERE crypto_code = $1 AND address = $2`
|
const sql = `DELETE FROM blacklist WHERE crypto_code = $1 AND address = $2`
|
||||||
notifier.clearBlacklistNotification(cryptoCode, address)
|
notifierQueries.clearBlacklistNotification(cryptoCode, address).catch(console.error)
|
||||||
return db.none(sql, [cryptoCode, address])
|
return db.none(sql, [cryptoCode, address])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ function post (machineTx, pi) {
|
||||||
|
|
||||||
if (_.some(it => it.created_by_operator)(blacklistItems)) {
|
if (_.some(it => it.created_by_operator)(blacklistItems)) {
|
||||||
blacklisted = true
|
blacklisted = true
|
||||||
notifier.blacklistNotify(r.tx, false)
|
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, false).catch(console.error)
|
||||||
} else if (_.some(it => !it.created_by_operator)(blacklistItems) && rejectAddressReuseActive) {
|
} else if (_.some(it => !it.created_by_operator)(blacklistItems) && rejectAddressReuseActive) {
|
||||||
notifier.blacklistNotify(r.tx, true)
|
notifier.notifyIfActive('compliance', 'blacklistNotify', r.tx, true).catch(console.error)
|
||||||
addressReuse = true
|
addressReuse = true
|
||||||
}
|
}
|
||||||
return postProcess(r, pi, blacklisted, addressReuse)
|
return postProcess(r, pi, blacklisted, addressReuse)
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,11 @@ const queries = require('./queries')
|
||||||
const settingsLoader = require('../new-settings-loader')
|
const settingsLoader = require('../new-settings-loader')
|
||||||
const customers = require('../customers')
|
const customers = require('../customers')
|
||||||
|
|
||||||
|
const notificationCenter = require('./notificationCenter')
|
||||||
const utils = require('./utils')
|
const utils = require('./utils')
|
||||||
const emailFuncs = require('./email')
|
const emailFuncs = require('./email')
|
||||||
const smsFuncs = require('./sms')
|
const smsFuncs = require('./sms')
|
||||||
const codes = require('./codes')
|
const { STALE, STALE_STATE } = require('./codes')
|
||||||
const { STALE, STALE_STATE, PING } = require('./codes')
|
|
||||||
|
|
||||||
const { NOTIFICATION_TYPES: {
|
|
||||||
HIGH_VALUE_TX,
|
|
||||||
NORMAL_VALUE_TX,
|
|
||||||
FIAT_BALANCE,
|
|
||||||
CRYPTO_BALANCE,
|
|
||||||
COMPLIANCE,
|
|
||||||
ERROR }
|
|
||||||
} = codes
|
|
||||||
|
|
||||||
function buildMessage (alerts, notifications) {
|
function buildMessage (alerts, notifications) {
|
||||||
const smsEnabled = utils.isActive(notifications.sms)
|
const smsEnabled = utils.isActive(notifications.sms)
|
||||||
|
|
@ -52,7 +43,7 @@ function checkNotification (plugins) {
|
||||||
|
|
||||||
return getAlerts(plugins)
|
return getAlerts(plugins)
|
||||||
.then(alerts => {
|
.then(alerts => {
|
||||||
notifyIfActive('errors', alerts).catch(console.error)
|
notifyIfActive('errors', 'errorAlertsNotify', alerts).catch(console.error)
|
||||||
const currentAlertFingerprint = utils.buildAlertFingerprint(
|
const currentAlertFingerprint = utils.buildAlertFingerprint(
|
||||||
alerts,
|
alerts,
|
||||||
notifications
|
notifications
|
||||||
|
|
@ -83,7 +74,7 @@ function getAlerts (plugins) {
|
||||||
queries.machineEvents(),
|
queries.machineEvents(),
|
||||||
plugins.getMachineNames()
|
plugins.getMachineNames()
|
||||||
]).then(([balances, events, devices]) => {
|
]).then(([balances, events, devices]) => {
|
||||||
notifyIfActive('balance', balances).catch(console.error)
|
notifyIfActive('balance', 'balancesNotify', balances).catch(console.error)
|
||||||
return buildAlerts(checkPings(devices), balances, events, devices)
|
return buildAlerts(checkPings(devices), balances, events, devices)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -138,13 +129,6 @@ function checkStuckScreen (deviceEvents, machineName) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifCenterTransactionNotify (isHighValue, direction, fiat, fiatCode, deviceId, cryptoAddress) {
|
|
||||||
const messageSuffix = isHighValue ? 'High value' : ''
|
|
||||||
const message = `${messageSuffix} ${fiat} ${fiatCode} ${direction} transaction`
|
|
||||||
const detailB = utils.buildDetail({ deviceId: deviceId, direction, fiat, fiatCode, cryptoAddress })
|
|
||||||
return queries.addNotification(isHighValue ? HIGH_VALUE_TX : NORMAL_VALUE_TX, message, detailB)
|
|
||||||
}
|
|
||||||
|
|
||||||
function transactionNotify (tx, rec) {
|
function transactionNotify (tx, rec) {
|
||||||
return settingsLoader.loadLatest().then(settings => {
|
return settingsLoader.loadLatest().then(settings => {
|
||||||
const notifSettings = configManager.getGlobalNotifications(settings.config)
|
const notifSettings = configManager.getGlobalNotifications(settings.config)
|
||||||
|
|
@ -154,8 +138,12 @@ function transactionNotify (tx, rec) {
|
||||||
// for notification center
|
// for notification center
|
||||||
const directionDisplay = tx.direction === 'cashOut' ? 'cash-out' : 'cash-in'
|
const directionDisplay = tx.direction === 'cashOut' ? 'cash-out' : 'cash-in'
|
||||||
const readyToNotify = tx.direction === 'cashIn' || (tx.direction === 'cashOut' && rec.isRedemption)
|
const readyToNotify = tx.direction === 'cashIn' || (tx.direction === 'cashOut' && rec.isRedemption)
|
||||||
if (readyToNotify) {
|
// awaiting for redesign. notification should not be sent if toggle in the settings table is disabled,
|
||||||
notifyIfActive('transactions', highValueTx, directionDisplay, tx.fiat, tx.fiatCode, tx.deviceId, tx.toAddress).catch(console.error)
|
// but currently we're sending notifications of high value tx even with the toggle disabled
|
||||||
|
if (readyToNotify && !highValueTx) {
|
||||||
|
notifyIfActive('transactions', 'notifCenterTransactionNotify', highValueTx, directionDisplay, tx.fiat, tx.fiatCode, tx.deviceId, tx.toAddress).catch(console.error)
|
||||||
|
} else if (readyToNotify && highValueTx) {
|
||||||
|
notificationCenter.notifCenterTransactionNotify(highValueTx, directionDisplay, tx.fiat, tx.fiatCode, tx.deviceId, tx.toAddress).catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
|
// alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
|
||||||
|
|
@ -216,169 +204,21 @@ function sendTransactionMessage (rec, isHighValueTx) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearOldCryptoNotifications = balances => {
|
|
||||||
return queries.getAllValidNotifications(CRYPTO_BALANCE).then(res => {
|
|
||||||
const filterByBalance = _.filter(notification => {
|
|
||||||
const { cryptoCode, code } = notification.detail
|
|
||||||
return !_.find(balance => balance.cryptoCode === cryptoCode && balance.code === code)(balances)
|
|
||||||
})
|
|
||||||
const indexesToInvalidate = _.compose(_.map('id'), filterByBalance)(res)
|
|
||||||
|
|
||||||
const notInvalidated = _.filter(notification => {
|
|
||||||
return !_.find(id => notification.id === id)(indexesToInvalidate)
|
|
||||||
}, res)
|
|
||||||
return (indexesToInvalidate.length ? queries.batchInvalidate(indexesToInvalidate) : Promise.resolve()).then(() => notInvalidated)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cryptoBalancesNotify = (cryptoWarnings) => {
|
|
||||||
return clearOldCryptoNotifications(cryptoWarnings).then(notInvalidated => {
|
|
||||||
return cryptoWarnings.forEach(balance => {
|
|
||||||
// if notification exists in DB and wasnt invalidated then don't add a duplicate
|
|
||||||
if (_.find(o => {
|
|
||||||
const { code, cryptoCode } = o.detail
|
|
||||||
return code === balance.code && cryptoCode === balance.cryptoCode
|
|
||||||
}, notInvalidated)) return
|
|
||||||
|
|
||||||
const fiat = utils.formatCurrency(balance.fiatBalance.balance, balance.fiatCode)
|
|
||||||
const message = `${balance.code === 'HIGH_CRYPTO_BALANCE' ? 'High' : 'Low'} balance in ${balance.cryptoCode} [${fiat}]`
|
|
||||||
const detailB = utils.buildDetail({ cryptoCode: balance.cryptoCode, code: balance.code })
|
|
||||||
return queries.addNotification(CRYPTO_BALANCE, message, detailB)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearOldFiatNotifications = (balances) => {
|
|
||||||
return queries.getAllValidNotifications(FIAT_BALANCE).then(notifications => {
|
|
||||||
const filterByBalance = _.filter(notification => {
|
|
||||||
const { cassette, deviceId } = notification.detail
|
|
||||||
return !_.find(balance => balance.cassette === cassette && balance.deviceId === deviceId)(balances)
|
|
||||||
})
|
|
||||||
const indexesToInvalidate = _.compose(_.map('id'), filterByBalance)(notifications)
|
|
||||||
const notInvalidated = _.filter(notification => {
|
|
||||||
return !_.find(id => notification.id === id)(indexesToInvalidate)
|
|
||||||
}, notifications)
|
|
||||||
return (indexesToInvalidate.length ? queries.batchInvalidate(indexesToInvalidate) : Promise.resolve()).then(() => notInvalidated)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fiatBalancesNotify = (fiatWarnings) => {
|
|
||||||
return clearOldFiatNotifications(fiatWarnings).then(notInvalidated => {
|
|
||||||
return fiatWarnings.forEach(balance => {
|
|
||||||
if (_.find(o => {
|
|
||||||
const { cassette, deviceId } = o.detail
|
|
||||||
return cassette === balance.cassette && deviceId === balance.deviceId
|
|
||||||
}, notInvalidated)) return
|
|
||||||
const message = `Cash-out cassette ${balance.cassette} almost empty!`
|
|
||||||
const detailB = utils.buildDetail({ deviceId: balance.deviceId, cassette: balance.cassette })
|
|
||||||
return queries.addNotification(FIAT_BALANCE, message, detailB)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const balancesNotify = (balances) => {
|
|
||||||
const cryptoFilter = o => o.code === 'HIGH_CRYPTO_BALANCE' || o.code === 'LOW_CRYPTO_BALANCE'
|
|
||||||
const fiatFilter = o => o.code === 'LOW_CASH_OUT'
|
|
||||||
const cryptoWarnings = _.filter(cryptoFilter, balances)
|
|
||||||
const fiatWarnings = _.filter(fiatFilter, balances)
|
|
||||||
return Promise.all([cryptoBalancesNotify(cryptoWarnings), fiatBalancesNotify(fiatWarnings)]).catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearOldErrorNotifications = alerts => {
|
|
||||||
return queries.getAllValidNotifications(ERROR)
|
|
||||||
.then(res => {
|
|
||||||
// for each valid notification in DB see if it exists in alerts
|
|
||||||
// if the notification doesn't exist in alerts, it is not valid anymore
|
|
||||||
const filterByAlert = _.filter(notification => {
|
|
||||||
const { code, deviceId } = notification.detail
|
|
||||||
return !_.find(alert => alert.code === code && alert.deviceId === deviceId)(alerts)
|
|
||||||
})
|
|
||||||
const indexesToInvalidate = _.compose(_.map('id'), filterByAlert)(res)
|
|
||||||
if (!indexesToInvalidate.length) return Promise.resolve()
|
|
||||||
return queries.batchInvalidate(indexesToInvalidate)
|
|
||||||
})
|
|
||||||
.catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorAlertsNotify = (alertRec) => {
|
|
||||||
const embedDeviceId = deviceId => _.assign({ deviceId })
|
|
||||||
const mapToAlerts = _.map(it => _.map(embedDeviceId(it), alertRec.devices[it].deviceAlerts))
|
|
||||||
const alerts = _.compose(_.flatten, mapToAlerts, _.keys)(alertRec.devices)
|
|
||||||
|
|
||||||
return clearOldErrorNotifications(alerts).then(() => {
|
|
||||||
_.forEach(alert => {
|
|
||||||
switch (alert.code) {
|
|
||||||
case PING: {
|
|
||||||
const detailB = utils.buildDetail({ code: PING, age: alert.age ? alert.age : -1, deviceId: alert.deviceId })
|
|
||||||
return queries.getValidNotifications(ERROR, _.omit(['age'], detailB)).then(res => {
|
|
||||||
if (res.length > 0) return Promise.resolve()
|
|
||||||
const message = `Machine down`
|
|
||||||
return queries.addNotification(ERROR, message, detailB)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case STALE: {
|
|
||||||
const detailB = utils.buildDetail({ code: STALE, deviceId: alert.deviceId })
|
|
||||||
return queries.getValidNotifications(ERROR, detailB).then(res => {
|
|
||||||
if (res.length > 0) return Promise.resolve()
|
|
||||||
const message = `Machine is stuck on ${alert.state} screen`
|
|
||||||
return queries.addNotification(ERROR, message, detailB)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, alerts)
|
|
||||||
}).catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const blacklistNotify = (tx, isAddressReuse) => {
|
|
||||||
const code = isAddressReuse ? 'REUSED' : 'BLOCKED'
|
|
||||||
const name = isAddressReuse ? 'reused' : 'blacklisted'
|
|
||||||
|
|
||||||
const detailB = utils.buildDetail({ cryptoCode: tx.cryptoCode, code, cryptoAddress: tx.toAddress })
|
|
||||||
const message = `Blocked ${name} address: ${tx.cryptoCode} ${tx.toAddress.substr(0, 10)}...`
|
|
||||||
return queries.addNotification(COMPLIANCE, message, detailB)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearBlacklistNotification = (cryptoCode, cryptoAddress) => {
|
|
||||||
return queries.clearBlacklistNotification(cryptoCode, cryptoAddress).catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearOldCustomerSuspendedNotifications = (customerId, deviceId) => {
|
|
||||||
const detailB = utils.buildDetail({ code: 'SUSPENDED', customerId, deviceId })
|
|
||||||
return queries.invalidateNotification(detailB, 'compliance')
|
|
||||||
}
|
|
||||||
|
|
||||||
const customerComplianceNotify = (customer, deviceId, code, days = null) => {
|
|
||||||
// code for now can be "BLOCKED", "SUSPENDED"
|
|
||||||
const detailB = utils.buildDetail({ customerId: customer.id, code, deviceId })
|
|
||||||
const date = new Date()
|
|
||||||
if (days) {
|
|
||||||
date.setDate(date.getDate() + days)
|
|
||||||
}
|
|
||||||
const message = code === 'SUSPENDED' ? `Customer suspended until ${date.toLocaleString()}` : `Customer blocked`
|
|
||||||
|
|
||||||
return clearOldCustomerSuspendedNotifications(customer.id, deviceId)
|
|
||||||
.then(() => queries.getValidNotifications(COMPLIANCE, detailB))
|
|
||||||
.then(res => {
|
|
||||||
if (res.length > 0) return Promise.resolve()
|
|
||||||
return queries.addNotification(COMPLIANCE, message, detailB)
|
|
||||||
})
|
|
||||||
.catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const notificationCenterFunctions = {
|
const notificationCenterFunctions = {
|
||||||
'compliance': customerComplianceNotify,
|
'customerComplianceNotify': notificationCenter.customerComplianceNotify,
|
||||||
'balance': balancesNotify,
|
'blacklistNotify': notificationCenter.blacklistNotify,
|
||||||
'errors': errorAlertsNotify,
|
'balancesNotify': notificationCenter.balancesNotify,
|
||||||
'transactions': notifCenterTransactionNotify
|
'errorAlertsNotify': notificationCenter.errorAlertsNotify,
|
||||||
|
'notifCenterTransactionNotify': notificationCenter.notifCenterTransactionNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
// for notification center, check if type of notification is active before calling the respective notify function
|
// for notification center, check if type of notification is active before calling the respective notify function
|
||||||
const notifyIfActive = (type, ...args) => {
|
const notifyIfActive = (type, fnName, ...args) => {
|
||||||
return settingsLoader.loadLatest().then(settings => {
|
return settingsLoader.loadLatest().then(settings => {
|
||||||
const notificationSettings = configManager.getGlobalNotifications(settings.config).notificationCenter
|
const notificationSettings = configManager.getGlobalNotifications(settings.config).notificationCenter
|
||||||
if (!notificationCenterFunctions[type]) return Promise.reject(new Error(`Notification of type ${type} does not exist`))
|
if (!notificationCenterFunctions[fnName]) return Promise.reject(new Error(`Notification function ${fnName} for type ${type} does not exist`))
|
||||||
if (!(notificationSettings.active && notificationSettings[type])) return Promise.resolve()
|
if (!(notificationSettings.active && notificationSettings[type])) return Promise.resolve()
|
||||||
return notificationCenterFunctions[type](...args)
|
return notificationCenterFunctions[fnName](...args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,7 +228,5 @@ module.exports = {
|
||||||
checkPings,
|
checkPings,
|
||||||
checkStuckScreen,
|
checkStuckScreen,
|
||||||
sendRedemptionMessage,
|
sendRedemptionMessage,
|
||||||
blacklistNotify,
|
|
||||||
clearBlacklistNotification,
|
|
||||||
notifyIfActive
|
notifyIfActive
|
||||||
}
|
}
|
||||||
|
|
|
||||||
170
lib/notifier/notificationCenter.js
Normal file
170
lib/notifier/notificationCenter.js
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const queries = require('./queries')
|
||||||
|
const utils = require('./utils')
|
||||||
|
const codes = require('./codes')
|
||||||
|
|
||||||
|
const { NOTIFICATION_TYPES: {
|
||||||
|
COMPLIANCE,
|
||||||
|
CRYPTO_BALANCE,
|
||||||
|
FIAT_BALANCE,
|
||||||
|
ERROR,
|
||||||
|
HIGH_VALUE_TX,
|
||||||
|
NORMAL_VALUE_TX }
|
||||||
|
} = codes
|
||||||
|
|
||||||
|
const { STALE, PING } = codes
|
||||||
|
|
||||||
|
const clearOldCustomerSuspendedNotifications = (customerId, deviceId) => {
|
||||||
|
const detailB = utils.buildDetail({ code: 'SUSPENDED', customerId, deviceId })
|
||||||
|
return queries.invalidateNotification(detailB, 'compliance')
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerComplianceNotify = (customer, deviceId, code, days = null) => {
|
||||||
|
// code for now can be "BLOCKED", "SUSPENDED"
|
||||||
|
const detailB = utils.buildDetail({ customerId: customer.id, code, deviceId })
|
||||||
|
const date = new Date()
|
||||||
|
if (days) {
|
||||||
|
date.setDate(date.getDate() + days)
|
||||||
|
}
|
||||||
|
const message = code === 'SUSPENDED' ? `Customer suspended until ${date.toLocaleString()}` : `Customer blocked`
|
||||||
|
|
||||||
|
return clearOldCustomerSuspendedNotifications(customer.id, deviceId)
|
||||||
|
.then(() => queries.getValidNotifications(COMPLIANCE, detailB))
|
||||||
|
.then(res => {
|
||||||
|
if (res.length > 0) return Promise.resolve()
|
||||||
|
return queries.addNotification(COMPLIANCE, message, detailB)
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearOldFiatNotifications = (balances) => {
|
||||||
|
return queries.getAllValidNotifications(FIAT_BALANCE).then(notifications => {
|
||||||
|
const filterByBalance = _.filter(notification => {
|
||||||
|
const { cassette, deviceId } = notification.detail
|
||||||
|
return !_.find(balance => balance.cassette === cassette && balance.deviceId === deviceId)(balances)
|
||||||
|
})
|
||||||
|
const indexesToInvalidate = _.compose(_.map('id'), filterByBalance)(notifications)
|
||||||
|
const notInvalidated = _.filter(notification => {
|
||||||
|
return !_.find(id => notification.id === id)(indexesToInvalidate)
|
||||||
|
}, notifications)
|
||||||
|
return (indexesToInvalidate.length ? queries.batchInvalidate(indexesToInvalidate) : Promise.resolve()).then(() => notInvalidated)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fiatBalancesNotify = (fiatWarnings) => {
|
||||||
|
return clearOldFiatNotifications(fiatWarnings).then(notInvalidated => {
|
||||||
|
return fiatWarnings.forEach(balance => {
|
||||||
|
if (_.find(o => {
|
||||||
|
const { cassette, deviceId } = o.detail
|
||||||
|
return cassette === balance.cassette && deviceId === balance.deviceId
|
||||||
|
}, notInvalidated)) return
|
||||||
|
const message = `Cash-out cassette ${balance.cassette} almost empty!`
|
||||||
|
const detailB = utils.buildDetail({ deviceId: balance.deviceId, cassette: balance.cassette })
|
||||||
|
return queries.addNotification(FIAT_BALANCE, message, detailB)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearOldCryptoNotifications = balances => {
|
||||||
|
return queries.getAllValidNotifications(CRYPTO_BALANCE).then(res => {
|
||||||
|
const filterByBalance = _.filter(notification => {
|
||||||
|
const { cryptoCode, code } = notification.detail
|
||||||
|
return !_.find(balance => balance.cryptoCode === cryptoCode && balance.code === code)(balances)
|
||||||
|
})
|
||||||
|
const indexesToInvalidate = _.compose(_.map('id'), filterByBalance)(res)
|
||||||
|
|
||||||
|
const notInvalidated = _.filter(notification => {
|
||||||
|
return !_.find(id => notification.id === id)(indexesToInvalidate)
|
||||||
|
}, res)
|
||||||
|
return (indexesToInvalidate.length ? queries.batchInvalidate(indexesToInvalidate) : Promise.resolve()).then(() => notInvalidated)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cryptoBalancesNotify = (cryptoWarnings) => {
|
||||||
|
return clearOldCryptoNotifications(cryptoWarnings).then(notInvalidated => {
|
||||||
|
return cryptoWarnings.forEach(balance => {
|
||||||
|
// if notification exists in DB and wasnt invalidated then don't add a duplicate
|
||||||
|
if (_.find(o => {
|
||||||
|
const { code, cryptoCode } = o.detail
|
||||||
|
return code === balance.code && cryptoCode === balance.cryptoCode
|
||||||
|
}, notInvalidated)) return
|
||||||
|
|
||||||
|
const fiat = utils.formatCurrency(balance.fiatBalance.balance, balance.fiatCode)
|
||||||
|
const message = `${balance.code === 'HIGH_CRYPTO_BALANCE' ? 'High' : 'Low'} balance in ${balance.cryptoCode} [${fiat}]`
|
||||||
|
const detailB = utils.buildDetail({ cryptoCode: balance.cryptoCode, code: balance.code })
|
||||||
|
return queries.addNotification(CRYPTO_BALANCE, message, detailB)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const balancesNotify = (balances) => {
|
||||||
|
const cryptoFilter = o => o.code === 'HIGH_CRYPTO_BALANCE' || o.code === 'LOW_CRYPTO_BALANCE'
|
||||||
|
const fiatFilter = o => o.code === 'LOW_CASH_OUT'
|
||||||
|
const cryptoWarnings = _.filter(cryptoFilter, balances)
|
||||||
|
const fiatWarnings = _.filter(fiatFilter, balances)
|
||||||
|
return Promise.all([cryptoBalancesNotify(cryptoWarnings), fiatBalancesNotify(fiatWarnings)]).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearOldErrorNotifications = alerts => {
|
||||||
|
return queries.getAllValidNotifications(ERROR)
|
||||||
|
.then(res => {
|
||||||
|
// for each valid notification in DB see if it exists in alerts
|
||||||
|
// if the notification doesn't exist in alerts, it is not valid anymore
|
||||||
|
const filterByAlert = _.filter(notification => {
|
||||||
|
const { code, deviceId } = notification.detail
|
||||||
|
return !_.find(alert => alert.code === code && alert.deviceId === deviceId)(alerts)
|
||||||
|
})
|
||||||
|
const indexesToInvalidate = _.compose(_.map('id'), filterByAlert)(res)
|
||||||
|
if (!indexesToInvalidate.length) return Promise.resolve()
|
||||||
|
return queries.batchInvalidate(indexesToInvalidate)
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorAlertsNotify = (alertRec) => {
|
||||||
|
const embedDeviceId = deviceId => _.assign({ deviceId })
|
||||||
|
const mapToAlerts = _.map(it => _.map(embedDeviceId(it), alertRec.devices[it].deviceAlerts))
|
||||||
|
const alerts = _.compose(_.flatten, mapToAlerts, _.keys)(alertRec.devices)
|
||||||
|
|
||||||
|
return clearOldErrorNotifications(alerts).then(() => {
|
||||||
|
_.forEach(alert => {
|
||||||
|
switch (alert.code) {
|
||||||
|
case PING: {
|
||||||
|
const detailB = utils.buildDetail({ code: PING, age: alert.age ? alert.age : -1, deviceId: alert.deviceId })
|
||||||
|
return queries.getValidNotifications(ERROR, _.omit(['age'], detailB)).then(res => {
|
||||||
|
if (res.length > 0) return Promise.resolve()
|
||||||
|
const message = `Machine down`
|
||||||
|
return queries.addNotification(ERROR, message, detailB)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case STALE: {
|
||||||
|
const detailB = utils.buildDetail({ code: STALE, deviceId: alert.deviceId })
|
||||||
|
return queries.getValidNotifications(ERROR, detailB).then(res => {
|
||||||
|
if (res.length > 0) return Promise.resolve()
|
||||||
|
const message = `Machine is stuck on ${alert.state} screen`
|
||||||
|
return queries.addNotification(ERROR, message, detailB)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, alerts)
|
||||||
|
}).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifCenterTransactionNotify (isHighValue, direction, fiat, fiatCode, deviceId, cryptoAddress) {
|
||||||
|
const messageSuffix = isHighValue ? 'High value' : ''
|
||||||
|
const message = `${messageSuffix} ${fiat} ${fiatCode} ${direction} transaction`
|
||||||
|
const detailB = utils.buildDetail({ deviceId: deviceId, direction, fiat, fiatCode, cryptoAddress })
|
||||||
|
return queries.addNotification(isHighValue ? HIGH_VALUE_TX : NORMAL_VALUE_TX, message, detailB)
|
||||||
|
}
|
||||||
|
|
||||||
|
const blacklistNotify = (tx, isAddressReuse) => {
|
||||||
|
const code = isAddressReuse ? 'REUSED' : 'BLOCKED'
|
||||||
|
const name = isAddressReuse ? 'reused' : 'blacklisted'
|
||||||
|
|
||||||
|
const detailB = utils.buildDetail({ cryptoCode: tx.cryptoCode, code, cryptoAddress: tx.toAddress })
|
||||||
|
const message = `Blocked ${name} address: ${tx.cryptoCode} ${tx.toAddress.substr(0, 10)}...`
|
||||||
|
return queries.addNotification(COMPLIANCE, message, detailB)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { customerComplianceNotify, balancesNotify, errorAlertsNotify, notifCenterTransactionNotify, blacklistNotify }
|
||||||
|
|
@ -291,9 +291,15 @@ test("calls sendRedemptionMessage if !zeroConf and rec.isRedemption", async () =
|
||||||
// sendRedemptionMessage will cause this func to be called
|
// sendRedemptionMessage will cause this func to be called
|
||||||
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => rec)
|
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => rec)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
getCashOut.mockReturnValue({zeroConfLimit: -Infinity})
|
getCashOut.mockReturnValue({zeroConfLimit: -Infinity})
|
||||||
loadLatest.mockReturnValue({})
|
loadLatest.mockReturnValue({})
|
||||||
getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: true }})
|
getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: true }})
|
||||||
|
=======
|
||||||
|
getCashOut.mockReturnValue({ zeroConfLimit: -Infinity })
|
||||||
|
loadLatest.mockReturnValue(Promise.resolve({}))
|
||||||
|
getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true }, notificationCenter: { active: true } })
|
||||||
|
>>>>>>> a7a9fd3... Feat: move notif center fns to own file on the notifier module
|
||||||
|
|
||||||
const response = await notifier.transactionNotify(tx, {isRedemption: true})
|
const response = await notifier.transactionNotify(tx, {isRedemption: true})
|
||||||
|
|
||||||
|
|
@ -324,10 +330,10 @@ test("calls sendTransactionMessage if !zeroConf and !rec.isRedemption", async ()
|
||||||
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => ({prop: rec}))
|
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => ({prop: rec}))
|
||||||
buildTransactionMessage.mockImplementation(() => ["mock message", false])
|
buildTransactionMessage.mockImplementation(() => ["mock message", false])
|
||||||
|
|
||||||
getMachineName.mockReturnValue("mockMachineName")
|
getMachineName.mockReturnValue('mockMachineName')
|
||||||
getCashOut.mockReturnValue({ zeroConfLimit: -Infinity })
|
getCashOut.mockReturnValue({ zeroConfLimit: -Infinity })
|
||||||
loadLatest.mockReturnValue({})
|
loadLatest.mockReturnValue(Promise.resolve({}))
|
||||||
getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: true }})
|
getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true }, notificationCenter: { active: true } })
|
||||||
|
|
||||||
const response = await notifier.transactionNotify(tx, {isRedemption: false})
|
const response = await notifier.transactionNotify(tx, {isRedemption: false})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ function triggerBlock (req, res, next) {
|
||||||
|
|
||||||
customers.update(id, { authorizedOverride: 'blocked' })
|
customers.update(id, { authorizedOverride: 'blocked' })
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
notifier.notifyIfActive('compliance', customer, req.deviceId, 'BLOCKED').catch(console.error)
|
notifier.notifyIfActive('compliance', 'customerComplianceNotify', customer, req.deviceId, 'BLOCKED').catch(console.error)
|
||||||
return respond(req, res, { customer })
|
return respond(req, res, { customer })
|
||||||
})
|
})
|
||||||
.catch(next)
|
.catch(next)
|
||||||
|
|
@ -362,7 +362,7 @@ function triggerSuspend (req, res, next) {
|
||||||
date.setDate(date.getDate() + days);
|
date.setDate(date.getDate() + days);
|
||||||
customers.update(id, { suspendedUntil: date })
|
customers.update(id, { suspendedUntil: date })
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
notifier.notifyIfActive('compliance', customer, req.deviceId, 'SUSPENDED', days).catch(console.error)
|
notifier.notifyIfActive('compliance', 'customerComplianceNotify', customer, req.deviceId, 'SUSPENDED', days).catch(console.error)
|
||||||
return respond(req, res, { customer })
|
return respond(req, res, { customer })
|
||||||
})
|
})
|
||||||
.catch(next)
|
.catch(next)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue