chore: server code formatting

This commit is contained in:
Rafael Taranto 2025-05-12 15:35:00 +01:00
parent aedabcbdee
commit 68517170e2
234 changed files with 9824 additions and 6195 deletions

View file

@ -18,7 +18,7 @@ const CODES_DISPLAY = {
LOW_CASH_OUT: 'Low Cash-out',
LOW_RECYCLER_STACKER: 'Low Recycler Stacker',
HIGH_RECYCLER_STACKER: 'High Recycler Stacker',
CASHBOX_REMOVED: 'Cashbox removed'
CASHBOX_REMOVED: 'Cashbox removed',
}
const NETWORK_DOWN_TIME = 3 * T.minute
@ -32,7 +32,7 @@ const NOTIFICATION_TYPES = {
CRYPTO_BALANCE: 'cryptoBalance',
COMPLIANCE: 'compliance',
ERROR: 'error',
SECURITY: 'security'
SECURITY: 'security',
}
module.exports = {
@ -48,5 +48,5 @@ module.exports = {
NETWORK_DOWN_TIME,
STALE_STATE,
ALERT_SEND_INTERVAL,
NOTIFICATION_TYPES
NOTIFICATION_TYPES,
}

View file

@ -11,10 +11,10 @@ const {
CASH_BOX_FULL,
LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
SECURITY
SECURITY,
} = require('./codes')
function alertSubject (alertRec, config) {
function alertSubject(alertRec, config) {
let alerts = []
if (config.balance) {
@ -27,11 +27,16 @@ function alertSubject (alertRec, config) {
if (alerts.length === 0) return null
const alertTypes = _.flow(_.map('code'), _.uniq, _.map(utils.codeDisplay), _.sortBy(o => o))(alerts)
const alertTypes = _.flow(
_.map('code'),
_.uniq,
_.map(utils.codeDisplay),
_.sortBy(o => o),
)(alerts)
return '[Lamassu] Errors reported: ' + alertTypes.join(', ')
}
function printEmailAlerts (alertRec, config) {
function printEmailAlerts(alertRec, config) {
let body = 'Errors were reported by your Lamassu Machines.\n'
if (config.balance && alertRec.general.length !== 0) {
@ -50,30 +55,39 @@ function printEmailAlerts (alertRec, config) {
return body
}
function emailAlerts (alerts) {
function emailAlerts(alerts) {
return _.join('\n', _.map(emailAlert, alerts)) + '\n'
}
function emailAlert (alert) {
function emailAlert(alert) {
switch (alert.code) {
case PING:
if (alert.age) {
const pingAge = utils.formatAge(alert.age, { compact: true, verbose: true })
const pingAge = utils.formatAge(alert.age, {
compact: true,
verbose: true,
})
return `Machine down for ${pingAge}`
}
return 'Machine down for a while.'
case STALE: {
const stuckAge = utils.formatAge(alert.age, { compact: true, verbose: true })
const stuckAge = utils.formatAge(alert.age, {
compact: true,
verbose: true,
})
return `Machine is stuck on ${alert.state} screen for ${stuckAge}`
}
case LOW_CRYPTO_BALANCE: {
const balance = utils.formatCurrency(alert.fiatBalance.balance, alert.fiatCode)
const balance = utils.formatCurrency(
alert.fiatBalance.balance,
alert.fiatCode,
)
return `Low balance in ${alert.cryptoCode} [${balance}]`
}
case HIGH_CRYPTO_BALANCE: {
const highBalance = utils.formatCurrency(
alert.fiatBalance.balance,
alert.fiatCode
alert.fiatCode,
)
return `High balance in ${alert.cryptoCode} [${highBalance}]`
}

View file

@ -13,48 +13,52 @@ const smsFuncs = require('./sms')
const webhookFuncs = require('./webhook')
const { STALE, STALE_STATE } = require('./codes')
function buildMessage (alerts, notifications) {
function buildMessage(alerts, notifications) {
const smsEnabled = utils.isActive(notifications.sms)
const emailEnabled = utils.isActive(notifications.email)
let rec = {}
if (smsEnabled) {
rec = _.set(['sms', 'body'])(
smsFuncs.printSmsAlerts(alerts, notifications.sms)
smsFuncs.printSmsAlerts(alerts, notifications.sms),
)(rec)
}
if (emailEnabled) {
rec = _.set(['email', 'subject'])(
emailFuncs.alertSubject(alerts, notifications.email)
emailFuncs.alertSubject(alerts, notifications.email),
)(rec)
rec = _.set(['email', 'body'])(
emailFuncs.printEmailAlerts(alerts, notifications.email)
emailFuncs.printEmailAlerts(alerts, notifications.email),
)(rec)
}
return rec
}
function checkNotification (plugins) {
function checkNotification(plugins) {
const notifications = plugins.getNotificationConfig()
const smsEnabled = utils.isActive(notifications.sms)
const emailEnabled = utils.isActive(notifications.email)
const notificationCenterEnabled = utils.isActive(notifications.notificationCenter)
const notificationCenterEnabled = utils.isActive(
notifications.notificationCenter,
)
if (!(notificationCenterEnabled || smsEnabled || emailEnabled)) return Promise.resolve()
if (!(notificationCenterEnabled || smsEnabled || emailEnabled))
return Promise.resolve()
return getAlerts(plugins)
.then(alerts => {
notifyIfActive('errors', 'errorAlertsNotify', alerts)
const currentAlertFingerprint = utils.buildAlertFingerprint(
alerts,
notifications
notifications,
)
if (!currentAlertFingerprint) {
const inAlert = !!utils.getAlertFingerprint()
// variables for setAlertFingerprint: (fingerprint = null, lastAlertTime = null)
utils.setAlertFingerprint(null, null)
if (inAlert) return utils.sendNoAlerts(plugins, smsEnabled, emailEnabled)
if (inAlert)
return utils.sendNoAlerts(plugins, smsEnabled, emailEnabled)
}
if (utils.shouldNotAlert(currentAlertFingerprint)) return
@ -70,18 +74,18 @@ function checkNotification (plugins) {
.catch(logger.error)
}
function getAlerts (plugins) {
function getAlerts(plugins) {
return Promise.all([
plugins.checkBalances(),
queries.machineEvents(),
plugins.getMachineNames()
plugins.getMachineNames(),
]).then(([balances, events, devices]) => {
notifyIfActive('balance', 'balancesNotify', balances)
return buildAlerts(checkPings(devices), balances, events, devices)
})
}
function buildAlerts (pings, balances, events, devices) {
function buildAlerts(pings, balances, events, devices) {
const alerts = { devices: {}, deviceNames: {} }
alerts.general = _.filter(r => !r.deviceId, balances)
_.forEach(device => {
@ -89,10 +93,11 @@ function buildAlerts (pings, balances, events, devices) {
const ping = pings[deviceId] || []
const stuckScreen = checkStuckScreen(events, device)
alerts.devices = _.set([deviceId, 'balanceAlerts'], _.filter(
['deviceId', deviceId],
balances
), alerts.devices)
alerts.devices = _.set(
[deviceId, 'balanceAlerts'],
_.filter(['deviceId', deviceId], balances),
alerts.devices,
)
alerts.devices[deviceId].deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping
alerts.deviceNames[deviceId] = device.name
@ -101,18 +106,18 @@ function buildAlerts (pings, balances, events, devices) {
return alerts
}
function checkPings (devices) {
function checkPings(devices) {
const deviceIds = _.map('deviceId', devices)
const pings = _.map(utils.checkPing, devices)
return _.zipObject(deviceIds)(pings)
}
function checkStuckScreen (deviceEvents, machine) {
function checkStuckScreen(deviceEvents, machine) {
const lastEvent = _.pipe(
_.filter(e => e.device_id === machine.deviceId),
_.sortBy(utils.getDeviceTime),
_.map(utils.parseEventNote),
_.last
_.last,
)(deviceEvents)
if (!lastEvent) return []
@ -129,115 +134,152 @@ function checkStuckScreen (deviceEvents, machine) {
return []
}
function transactionNotify (tx, rec) {
function transactionNotify(tx, rec) {
return settingsLoader.loadLatestConfig().then(config => {
const notifSettings = configManager.getGlobalNotifications(config)
const highValueTx = tx.fiat.gt(notifSettings.highValueTransaction || Infinity)
const highValueTx = tx.fiat.gt(
notifSettings.highValueTransaction || Infinity,
)
const isCashOut = tx.direction === 'cashOut'
// for notification center
const directionDisplay = isCashOut ? 'cash-out' : 'cash-in'
const readyToNotify = !isCashOut || (tx.direction === 'cashOut' && rec.isRedemption)
const readyToNotify =
!isCashOut || (tx.direction === 'cashOut' && rec.isRedemption)
// awaiting for redesign. notification should not be sent if toggle in the settings table is disabled,
// 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)
notifyIfActive(
'transactions',
'notifCenterTransactionNotify',
highValueTx,
directionDisplay,
tx.fiat,
tx.fiatCode,
tx.deviceId,
tx.toAddress,
)
} else if (readyToNotify && highValueTx) {
notificationCenter.notifCenterTransactionNotify(highValueTx, directionDisplay, tx.fiat, tx.fiatCode, tx.deviceId, tx.toAddress)
notificationCenter.notifCenterTransactionNotify(
highValueTx,
directionDisplay,
tx.fiat,
tx.fiatCode,
tx.deviceId,
tx.toAddress,
)
}
// alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
const walletSettings = configManager.getWalletSettings(tx.cryptoCode, config)
const walletSettings = configManager.getWalletSettings(
tx.cryptoCode,
config,
)
const zeroConfLimit = walletSettings.zeroConfLimit || 0
const zeroConf = isCashOut && tx.fiat.lte(zeroConfLimit)
const notificationsEnabled = notifSettings.sms.transactions || notifSettings.email.transactions
const customerPromise = tx.customerId ? customers.getById(tx.customerId) : Promise.resolve({})
const notificationsEnabled =
notifSettings.sms.transactions || notifSettings.email.transactions
const customerPromise = tx.customerId
? customers.getById(tx.customerId)
: Promise.resolve({})
if (!notificationsEnabled && !highValueTx) return Promise.resolve()
if (zeroConf && isCashOut && !rec.isRedemption && !rec.error) return Promise.resolve()
if (!zeroConf && rec.isRedemption) return sendRedemptionMessage(tx.id, rec.error)
if (zeroConf && isCashOut && !rec.isRedemption && !rec.error)
return Promise.resolve()
if (!zeroConf && rec.isRedemption)
return sendRedemptionMessage(tx.id, rec.error)
return Promise.all([
queries.getMachineName(tx.deviceId),
customerPromise
]).then(([machineName, customer]) => {
return utils.buildTransactionMessage(tx, rec, highValueTx, machineName, customer)
}).then(([msg, highValueTx]) => sendTransactionMessage(msg, highValueTx))
return Promise.all([queries.getMachineName(tx.deviceId), customerPromise])
.then(([machineName, customer]) => {
return utils.buildTransactionMessage(
tx,
rec,
highValueTx,
machineName,
customer,
)
})
.then(([msg, highValueTx]) => sendTransactionMessage(msg, highValueTx))
})
}
function complianceNotify (settings, customer, deviceId, action, period) {
const timestamp = (new Date()).toLocaleString()
return queries.getMachineName(deviceId)
.then(machineName => {
const notifications = configManager.getGlobalNotifications(settings.config)
function complianceNotify(settings, customer, deviceId, action, period) {
const timestamp = new Date().toLocaleString()
return queries.getMachineName(deviceId).then(machineName => {
const notifications = configManager.getGlobalNotifications(settings.config)
const msgCore = {
BLOCKED: `was blocked`,
SUSPENDED: `was suspended for ${!!period && period} days`,
PENDING_COMPLIANCE: `is waiting for your manual approval`,
}
const msgCore = {
BLOCKED: `was blocked`,
SUSPENDED: `was suspended for ${!!period && period} days`,
PENDING_COMPLIANCE: `is waiting for your manual approval`,
}
const rec = {
sms: {
body: `Customer ${customer.phone} ${msgCore[action]} - ${machineName}. ${timestamp}`
},
email: {
subject: `Customer compliance`,
body: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`
},
webhook: {
topic: `Customer compliance`,
content: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`
}
}
const rec = {
sms: {
body: `Customer ${customer.phone} ${msgCore[action]} - ${machineName}. ${timestamp}`,
},
email: {
subject: `Customer compliance`,
body: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`,
},
webhook: {
topic: `Customer compliance`,
content: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`,
},
}
const promises = []
const promises = []
const emailActive =
notifications.email.active &&
notifications.email.compliance
const emailActive =
notifications.email.active && notifications.email.compliance
const smsActive =
notifications.sms.active &&
notifications.sms.compliance
const smsActive = notifications.sms.active && notifications.sms.compliance
const webhookActive = true
const webhookActive = true
if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
notifyIfActive('compliance', 'customerComplianceNotify', customer, deviceId, action, machineName, period)
notifyIfActive(
'compliance',
'customerComplianceNotify',
customer,
deviceId,
action,
machineName,
period,
)
return Promise.all(promises)
.catch(err => console.error(`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`))
})
return Promise.all(promises).catch(err =>
console.error(
`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`,
),
)
})
}
function sendRedemptionMessage (txId, error) {
function sendRedemptionMessage(txId, error) {
const subject = `Here's an update on transaction ${txId}`
const body = error
? `Error: ${error}`
: 'It was just dispensed successfully'
const body = error ? `Error: ${error}` : 'It was just dispensed successfully'
const rec = {
sms: {
body: `${subject} - ${body}`
body: `${subject} - ${body}`,
},
email: {
subject,
body
body,
},
webhook: {
topic: `Transaction update`,
content: body
}
content: body,
},
}
return sendTransactionMessage(rec)
}
function sendTransactionMessage (rec, isHighValueTx) {
function sendTransactionMessage(rec, isHighValueTx) {
return settingsLoader.loadLatest().then(settings => {
const notifications = configManager.getGlobalNotifications(settings.config)
@ -258,62 +300,74 @@ function sendTransactionMessage (rec, isHighValueTx) {
const webhookActive = true
if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
return Promise.all(promises)
.catch(err => console.error(`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`))
return Promise.all(promises).catch(err =>
console.error(
`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`,
),
)
})
}
function cashboxNotify (deviceId) {
function cashboxNotify(deviceId) {
return Promise.all([
settingsLoader.loadLatest(),
queries.getMachineName(deviceId)
])
.then(([settings, machineName]) => {
const notifications = configManager.getGlobalNotifications(settings.config)
const rec = {
sms: {
body: `Cashbox removed - ${machineName}`
},
email: {
subject: `Cashbox removal`,
body: `Cashbox removed in machine ${machineName}`
},
webhook: {
topic: `Cashbox removal`,
content: `Cashbox removed in machine ${machineName}`
}
}
queries.getMachineName(deviceId),
]).then(([settings, machineName]) => {
const notifications = configManager.getGlobalNotifications(settings.config)
const rec = {
sms: {
body: `Cashbox removed - ${machineName}`,
},
email: {
subject: `Cashbox removal`,
body: `Cashbox removed in machine ${machineName}`,
},
webhook: {
topic: `Cashbox removal`,
content: `Cashbox removed in machine ${machineName}`,
},
}
const promises = []
const promises = []
const emailActive =
notifications.email.active &&
notifications.email.security
const emailActive =
notifications.email.active && notifications.email.security
const smsActive =
notifications.sms.active &&
notifications.sms.security
const smsActive = notifications.sms.active && notifications.sms.security
const webhookActive = true
if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
notifyIfActive('security', 'cashboxNotify', deviceId)
const webhookActive = true
return Promise.all(promises)
.catch(err => console.error(`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`))
})
if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
notifyIfActive('security', 'cashboxNotify', deviceId)
return Promise.all(promises).catch(err =>
console.error(
`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`,
),
)
})
}
// for notification center, check if type of notification is active before calling the respective notify function
const notifyIfActive = (type, fnName, ...args) => {
return settingsLoader.loadLatestConfig().then(config => {
const notificationSettings = configManager.getGlobalNotifications(config).notificationCenter
if (!notificationCenter[fnName]) return Promise.reject(new Error(`Notification function ${fnName} for type ${type} does not exist`))
if (!(notificationSettings.active && notificationSettings[type])) return Promise.resolve()
return notificationCenter[fnName](...args)
}).catch(logger.error)
return settingsLoader
.loadLatestConfig()
.then(config => {
const notificationSettings =
configManager.getGlobalNotifications(config).notificationCenter
if (!notificationCenter[fnName])
return Promise.reject(
new Error(
`Notification function ${fnName} for type ${type} does not exist`,
),
)
if (!(notificationSettings.active && notificationSettings[type]))
return Promise.resolve()
return notificationCenter[fnName](...args)
})
.catch(logger.error)
}
module.exports = {
@ -324,5 +378,5 @@ module.exports = {
checkStuckScreen,
sendRedemptionMessage,
cashboxNotify,
notifyIfActive
notifyIfActive,
}

View file

@ -11,7 +11,7 @@ const {
FIAT_BALANCE,
ERROR,
HIGH_VALUE_TX,
NORMAL_VALUE_TX
NORMAL_VALUE_TX,
},
STALE,
@ -28,9 +28,15 @@ const sanctionsNotify = (customer, phone) => {
const code = 'SANCTIONS'
const detailB = utils.buildDetail({ customerId: customer.id, code })
const addNotif = phone =>
queries.addNotification(COMPLIANCE, `Blocked customer with phone ${phone} for being on the OFAC sanctions list`, detailB)
queries.addNotification(
COMPLIANCE,
`Blocked customer with phone ${phone} for being on the OFAC sanctions list`,
detailB,
)
// if it's a new customer then phone comes as undefined
return phone ? addNotif(phone) : customers.getById(customer.id).then(c => addNotif(c.phone))
return phone
? addNotif(phone)
: customers.getById(customer.id).then(c => addNotif(c.phone))
}
const clearOldCustomerSuspendedNotifications = (customerId, deviceId) => {
@ -38,16 +44,25 @@ const clearOldCustomerSuspendedNotifications = (customerId, deviceId) => {
return queries.invalidateNotification(detailB, 'compliance')
}
const customerComplianceNotify = (customer, deviceId, code, machineName, days = null) => {
const customerComplianceNotify = (
customer,
deviceId,
code,
machineName,
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 ${customer.phone} suspended until ${date.toLocaleString()}` :
code === 'BLOCKED' ? `Customer ${customer.phone} blocked` :
`Customer ${customer.phone} has pending compliance in machine ${machineName}`
const message =
code === 'SUSPENDED'
? `Customer ${customer.phone} suspended until ${date.toLocaleString()}`
: code === 'BLOCKED'
? `Customer ${customer.phone} blocked`
: `Customer ${customer.phone} has pending compliance in machine ${machineName}`
return clearOldCustomerSuspendedNotifications(customer.id, deviceId)
.then(() => queries.getValidNotifications(COMPLIANCE, detailB))
@ -57,35 +72,52 @@ const customerComplianceNotify = (customer, deviceId, code, machineName, days =
})
}
const clearOldFiatNotifications = (balances) => {
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)
return !_.find(
balance =>
balance.cassette === cassette && balance.deviceId === deviceId,
)(balances)
})
const indexesToInvalidate = _.compose(_.map('id'), filterByBalance)(notifications)
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)
return (
indexesToInvalidate.length
? queries.batchInvalidate(indexesToInvalidate)
: Promise.resolve()
).then(() => notInvalidated)
})
}
const fiatBalancesNotify = (fiatWarnings) => {
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 = balance.code === LOW_CASH_OUT ?
`Cash-out cassette ${balance.cassette} low or empty!` :
balance.code === LOW_RECYCLER_STACKER ?
`Recycler ${balance.cassette} low or empty!` :
balance.code === CASH_BOX_FULL ?
`Cash box full or almost full!` :
`Cash box full or almost full!` /* Shouldn't happen */
const detailB = utils.buildDetail({ deviceId: balance.deviceId, cassette: balance.cassette })
if (
_.find(o => {
const { cassette, deviceId } = o.detail
return cassette === balance.cassette && deviceId === balance.deviceId
}, notInvalidated)
)
return
const message =
balance.code === LOW_CASH_OUT
? `Cash-out cassette ${balance.cassette} low or empty!`
: balance.code === LOW_RECYCLER_STACKER
? `Recycler ${balance.cassette} low or empty!`
: balance.code === CASH_BOX_FULL
? `Cash box full or almost full!`
: `Cash box full or almost full!` /* Shouldn't happen */
const detailB = utils.buildDetail({
deviceId: balance.deviceId,
cassette: balance.cassette,
})
return queries.addNotification(FIAT_BALANCE, message, detailB)
})
})
@ -95,82 +127,112 @@ 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)
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)
return (
indexesToInvalidate.length
? queries.batchInvalidate(indexesToInvalidate)
: Promise.resolve()
).then(() => notInvalidated)
})
}
const cryptoBalancesNotify = (cryptoWarnings) => {
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
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 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 })
const detailB = utils.buildDetail({
cryptoCode: balance.cryptoCode,
code: balance.code,
})
return queries.addNotification(CRYPTO_BALANCE, message, detailB)
})
})
}
const balancesNotify = (balances) => {
const isCryptoCode = c => _.includes(c, [HIGH_CRYPTO_BALANCE, LOW_CRYPTO_BALANCE])
const isFiatCode = c => _.includes(c, [LOW_CASH_OUT, CASH_BOX_FULL, LOW_RECYCLER_STACKER])
const balancesNotify = balances => {
const isCryptoCode = c =>
_.includes(c, [HIGH_CRYPTO_BALANCE, LOW_CRYPTO_BALANCE])
const isFiatCode = c =>
_.includes(c, [LOW_CASH_OUT, CASH_BOX_FULL, LOW_RECYCLER_STACKER])
const by = o =>
isCryptoCode(o) ? 'crypto' :
isFiatCode(o) ? 'fiat' :
undefined
isCryptoCode(o) ? 'crypto' : isFiatCode(o) ? 'fiat' : undefined
const warnings = _.flow(
_.groupBy(_.flow(_.get(['code']), by)),
_.update('crypto', _.defaultTo([])),
_.update('fiat', _.defaultTo([])),
)(balances)
return Promise.all([cryptoBalancesNotify(warnings.crypto), fiatBalancesNotify(warnings.fiat)])
return Promise.all([
cryptoBalancesNotify(warnings.crypto),
fiatBalancesNotify(warnings.fiat),
])
}
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)
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)
})
}
const errorAlertsNotify = (alertRec) => {
const errorAlertsNotify = alertRec => {
const embedDeviceId = deviceId => _.assign({ deviceId })
const mapToAlerts = _.map(it => _.map(embedDeviceId(it), alertRec.devices[it].deviceAlerts))
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)
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 })
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`
@ -182,18 +244,39 @@ const errorAlertsNotify = (alertRec) => {
})
}
function notifCenterTransactionNotify (isHighValue, direction, fiat, fiatCode, deviceId, cryptoAddress) {
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 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 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)
}
@ -211,5 +294,5 @@ module.exports = {
errorAlertsNotify,
notifCenterTransactionNotify,
blacklistNotify,
cashboxNotify
cashboxNotify,
}

View file

@ -15,18 +15,22 @@ compliance - notifications related to warnings triggered by compliance settings
error - notifications related to errors
*/
function getMachineName (machineId) {
function getMachineName(machineId) {
const sql = 'SELECT * FROM devices WHERE device_id=$1'
return db.oneOrNone(sql, [machineId])
.then(it => it.name).catch(logger.error)
return db
.oneOrNone(sql, [machineId])
.then(it => it.name)
.catch(logger.error)
}
const addNotification = (type, message, detail) => {
const sql = `INSERT INTO notifications (id, type, message, detail) VALUES ($1, $2, $3, $4)`
return db.oneOrNone(sql, [uuidv4(), type, message, detail]).catch(logger.error)
return db
.oneOrNone(sql, [uuidv4(), type, message, detail])
.catch(logger.error)
}
const getAllValidNotifications = (type) => {
const getAllValidNotifications = type => {
const sql = `SELECT * FROM notifications WHERE type = $1 AND valid = 't'`
return db.any(sql, [type]).catch(logger.error)
}
@ -37,7 +41,7 @@ const invalidateNotification = (detail, type) => {
return db.none(sql, [type, detail]).catch(logger.error)
}
const batchInvalidate = (ids) => {
const batchInvalidate = ids => {
const formattedIds = _.map(pgp.as.text, ids).join(',')
const sql = `UPDATE notifications SET valid = 'f', read = 't' WHERE id IN ($1^)`
return db.none(sql, [formattedIds]).catch(logger.error)
@ -79,7 +83,10 @@ const hasUnreadNotifications = () => {
(SELECT * FROM notifications
WHERE NOT read AND ${WITHIN_PAST_WEEK})
`
return db.oneOrNone(sql).then(res => res.exists).catch(logger.error)
return db
.oneOrNone(sql)
.then(res => res.exists)
.catch(logger.error)
}
const getAlerts = () => {
@ -105,5 +112,5 @@ module.exports = {
markAllAsRead,
hasUnreadNotifications,
getAlerts,
getMachineName
getMachineName,
}

View file

@ -2,7 +2,7 @@ const _ = require('lodash/fp')
const utils = require('./utils')
const sms = require('../sms')
function printSmsAlerts (alertRec, config) {
function printSmsAlerts(alertRec, config) {
let alerts = []
if (config.balance) {
@ -21,31 +21,33 @@ function printSmsAlerts (alertRec, config) {
const code = entry[0]
const machineNames = _.filter(
_.negate(_.isEmpty),
_.map('machineName', entry[1])
_.map('machineName', entry[1]),
)
const cryptoCodes = _.filter(
_.negate(_.isEmpty),
_.map('cryptoCode', entry[1])
_.map('cryptoCode', entry[1]),
)
return {
codeDisplay: utils.codeDisplay(code),
machineNames,
cryptoCodes
cryptoCodes,
}
}, _.toPairs(alertsMap))
const mapByCodeDisplay = _.map(it => {
if (_.isEmpty(it.machineNames) && _.isEmpty(it.cryptoCodes)) return it.codeDisplay
if (_.isEmpty(it.machineNames)) return `${it.codeDisplay} (${it.cryptoCodes.join(', ')})`
if (_.isEmpty(it.machineNames) && _.isEmpty(it.cryptoCodes))
return it.codeDisplay
if (_.isEmpty(it.machineNames))
return `${it.codeDisplay} (${it.cryptoCodes.join(', ')})`
return `${it.codeDisplay} (${it.machineNames.join(', ')})`
})
const displayAlertTypes = _.compose(
_.uniq,
mapByCodeDisplay,
_.sortBy('codeDisplay')
_.sortBy('codeDisplay'),
)(alertTypes)
return '[Lamassu] Errors reported: ' + displayAlertTypes.join(', ')

View file

@ -5,14 +5,14 @@ const alertRec = {
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: {
balanceAlerts: [],
deviceAlerts: [
{ code: 'PING', age: 602784301.446, machineName: 'Abc123' }
]
}
{ code: 'PING', age: 602784301.446, machineName: 'Abc123' },
],
},
},
deviceNames: {
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: 'Abc123'
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: 'Abc123',
},
general: []
general: [],
}
const printEmailMsg = `Errors were reported by your Lamassu Machines.
@ -23,6 +23,6 @@ Machine down for ~6 days
test('Print Email Alers', () => {
expect(email.printEmailAlerts(alertRec, { active: true, errors: true })).toBe(
printEmailMsg
printEmailMsg,
)
})

View file

@ -20,7 +20,7 @@ const plugins = {
email_errors: false,
sms_errors: true,
sms: { active: true, errors: true },
email: { active: false, errors: false }
email: { active: false, errors: false },
}),
getMachineNames: () => [
{
@ -36,16 +36,15 @@ const plugins = {
name: 'Abc123',
paired: true,
cashOut: true,
statuses: [{ label: 'Unresponsive', type: 'error' }]
}
statuses: [{ label: 'Unresponsive', type: 'error' }],
},
],
checkBalances: () => []
checkBalances: () => [],
}
const tx = {
id: 'bec8d452-9ea2-4846-841b-55a9df8bbd00',
deviceId:
'490ab16ee0c124512dc769be1f3e7ee3894ce1e5b4b8b975e134fb326e551e88',
deviceId: '490ab16ee0c124512dc769be1f3e7ee3894ce1e5b4b8b975e134fb326e551e88',
toAddress: 'bc1q7s4yy5n9vp6zhlf6mrw3cttdgx5l3ysr2mhc4v',
cryptoAtoms: new BN(252100),
cryptoCode: 'BTC',
@ -71,7 +70,7 @@ const tx = {
commissionPercentage: new BN(0.11),
rawTickerPrice: new BN(18937.4),
isPaperWallet: false,
direction: 'cashIn'
direction: 'cashIn',
}
const notifSettings = {
@ -84,13 +83,13 @@ const notifSettings = {
sms: {
active: true,
errors: true,
transactions: false // force early return
transactions: false, // force early return
},
email: {
active: false,
errors: false,
transactions: false // force early return
}
transactions: false, // force early return
},
}
test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled', async () => {
@ -99,9 +98,9 @@ test('Exits checkNotifications with Promise.resolve() if SMS and Email are disab
notifier.checkNotification({
getNotificationConfig: () => ({
sms: { active: false, errors: false },
email: { active: false, errors: false }
})
})
email: { active: false, errors: false },
}),
}),
).resolves.toBe(undefined)
})
@ -111,9 +110,9 @@ test('Exits checkNotifications with Promise.resolve() if SMS and Email are disab
notifier.checkNotification({
getNotificationConfig: () => ({
sms: { active: false, errors: true, balance: true },
email: { active: false, errors: true, balance: true }
})
})
email: { active: false, errors: true, balance: true },
}),
}),
).resolves.toBe(undefined)
})
@ -124,13 +123,13 @@ test("Check Pings should return code PING for devices that haven't been pinged r
deviceId:
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
lastPing: '2020-11-16T13:11:03.169Z',
name: 'Abc123'
}
])
name: 'Abc123',
},
]),
).toMatchObject({
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': [
{ code: 'PING', machineName: 'Abc123' }
]
{ code: 'PING', machineName: 'Abc123' },
],
})
})
@ -141,11 +140,11 @@ test('Checkpings returns empty array as the value for the id prop, if the lastPi
deviceId:
'7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
lastPing: new Date(),
name: 'Abc123'
}
])
name: 'Abc123',
},
]),
).toMatchObject({
'7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': []
'7a531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4': [],
})
})
@ -203,7 +202,7 @@ test('checkStuckScreen returns [] if most recent event is idle', () => {
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '1999-11-23T19:30:29.177Z',
age: 157352628.123
age: 157352628.123,
},
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
@ -213,9 +212,9 @@ test('checkStuckScreen returns [] if most recent event is idle', () => {
note: '{"state":"chooseCoin","isIdle":true}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 157352628.123
}
])
age: 157352628.123,
},
]),
).toEqual([])
})
@ -230,7 +229,7 @@ test('checkStuckScreen returns object array of length 1 with prop code: "STALE"
note: '{"state":"chooseCoin","isIdle":true}',
created: '2020-11-23T19:30:29.209Z',
device_time: '1999-11-23T19:30:29.177Z',
age: 0
age: 0,
},
{
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
@ -240,8 +239,8 @@ test('checkStuckScreen returns object array of length 1 with prop code: "STALE"
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 157352628.123
}
age: 157352628.123,
},
])
expect(result[0]).toMatchObject({ code: 'STALE' })
})
@ -257,8 +256,8 @@ test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: 0
}
age: 0,
},
])
const result2 = notifier.checkStuckScreen([
{
@ -269,8 +268,8 @@ test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
note: '{"state":"chooseCoin","isIdle":false}',
created: '2020-11-23T19:30:29.209Z',
device_time: '2020-11-23T19:30:29.177Z',
age: STALE_STATE
}
age: STALE_STATE,
},
])
expect(result1).toEqual([])
expect(result2).toEqual([])
@ -281,7 +280,10 @@ test('calls sendRedemptionMessage if !zeroConf and rec.isRedemption', async () =
const settingsLoader = require('../../new-settings-loader')
const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
const getGlobalNotifications = jest.spyOn(
configManager,
'getGlobalNotifications',
)
const getWalletSettings = jest.spyOn(configManager, 'getWalletSettings')
// sendRedemptionMessage will cause this func to be called
@ -289,19 +291,24 @@ test('calls sendRedemptionMessage if !zeroConf and rec.isRedemption', async () =
getWalletSettings.mockReturnValue({ zeroConfLimit: -Infinity })
loadLatest.mockReturnValue(Promise.resolve({}))
getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true }, notificationCenter: { active: true } })
getGlobalNotifications.mockReturnValue({
...notifSettings,
sms: { active: true, errors: true, transactions: true },
notificationCenter: { active: true },
})
const response = await notifier.transactionNotify(tx, { isRedemption: true })
// this type of response implies sendRedemptionMessage was called
expect(response[0]).toMatchObject({
sms: {
body: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00 - It was just dispensed successfully"
body: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00 - It was just dispensed successfully",
},
email: {
subject: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00",
body: 'It was just dispensed successfully'
}
subject:
"Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00",
body: 'It was just dispensed successfully',
},
})
})
@ -310,23 +317,32 @@ test('calls sendTransactionMessage if !zeroConf and !rec.isRedemption', async ()
const settingsLoader = require('../../new-settings-loader')
const queries = require('../queries')
const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
const getGlobalNotifications = jest.spyOn(
configManager,
'getGlobalNotifications',
)
const getWalletSettings = jest.spyOn(configManager, 'getWalletSettings')
const getMachineName = jest.spyOn(queries, 'getMachineName')
const buildTransactionMessage = jest.spyOn(utils, 'buildTransactionMessage')
// sendMessage on emailFuncs isn't called because it is disabled in getGlobalNotifications.mockReturnValue
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => ({ prop: rec }))
jest
.spyOn(smsFuncs, 'sendMessage')
.mockImplementation((_, rec) => ({ prop: rec }))
buildTransactionMessage.mockImplementation(() => ['mock message', false])
getMachineName.mockResolvedValue('mockMachineName')
getWalletSettings.mockReturnValue({ zeroConfLimit: -Infinity })
loadLatest.mockReturnValue(Promise.resolve({}))
getGlobalNotifications.mockReturnValue({ ...notifSettings, sms: { active: true, errors: true, transactions: true }, notificationCenter: { active: true } })
getGlobalNotifications.mockReturnValue({
...notifSettings,
sms: { active: true, errors: true, transactions: true },
notificationCenter: { active: true },
})
const response = await notifier.transactionNotify(tx, { isRedemption: false })
// If the return object is this, it means the code went through all the functions expected to go through if
// getMachineName, buildTransactionMessage and sendTransactionMessage were called, in this order
expect(response).toEqual([{prop: 'mock message'}])
expect(response).toEqual([{ prop: 'mock message' }])
})

View file

@ -5,18 +5,18 @@ const alertRec = {
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: {
balanceAlerts: [],
deviceAlerts: [
{ code: 'PING', age: 602784301.446, machineName: 'Abc123' }
]
}
{ code: 'PING', age: 602784301.446, machineName: 'Abc123' },
],
},
},
deviceNames: {
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: 'Abc123'
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: 'Abc123',
},
general: []
general: [],
}
test('Print SMS alerts', () => {
expect(sms.printSmsAlerts(alertRec, { active: true, errors: true })).toBe(
'[Lamassu] Errors reported: Machine Down (Abc123)'
'[Lamassu] Errors reported: Machine Down (Abc123)',
)
})

View file

@ -3,7 +3,7 @@ const utils = require('../utils')
const plugins = {
sendMessage: rec => {
return rec
}
},
}
const alertRec = {
@ -11,19 +11,19 @@ const alertRec = {
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: {
balanceAlerts: [],
deviceAlerts: [
{ code: 'PING', age: 1605532263169, machineName: 'Abc123' }
]
}
{ code: 'PING', age: 1605532263169, machineName: 'Abc123' },
],
},
},
deviceNames: {
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: 'Abc123'
f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05: 'Abc123',
},
general: []
general: [],
}
const notifications = {
sms: { active: true, errors: true },
email: { active: false, errors: false }
email: { active: false, errors: false },
}
describe('buildAlertFingerprint', () => {
@ -33,37 +33,37 @@ describe('buildAlertFingerprint', () => {
{
devices: {},
deviceNames: {},
general: []
general: [],
},
notifications
)
notifications,
),
).toBe(null)
})
test('Build alert fingerprint returns null if sms and email are disabled', () => {
expect(
utils.buildAlertFingerprint(alertRec, {
sms: { active: false, errors: true },
email: { active: false, errors: false }
})
email: { active: false, errors: false },
}),
).toBe(null)
})
test('Build alert fingerprint returns hash if email or [sms] are enabled and there are alerts in alertrec', () => {
expect(
typeof utils.buildAlertFingerprint(alertRec, {
sms: { active: true, errors: true },
email: { active: false, errors: false }
})
email: { active: false, errors: false },
}),
).toBe('string')
})
test('Build alert fingerprint returns hash if [email] or sms are enabled and there are alerts in alertrec', () => {
expect(
typeof utils.buildAlertFingerprint(alertRec, {
sms: { active: false, errors: false },
email: { active: true, errors: true }
})
email: { active: true, errors: true },
}),
).toBe('string')
})
})
@ -76,8 +76,8 @@ describe('sendNoAlerts', () => {
test('Send no alerts returns object with sms prop with sms only enabled', () => {
expect(utils.sendNoAlerts(plugins, true, false)).toEqual({
sms: {
body: '[Lamassu] All clear'
}
body: '[Lamassu] All clear',
},
})
})
@ -85,11 +85,11 @@ describe('sendNoAlerts', () => {
expect(utils.sendNoAlerts(plugins, true, true)).toEqual({
email: {
body: 'No errors are reported for your machines.',
subject: '[Lamassu] All clear'
subject: '[Lamassu] All clear',
},
sms: {
body: '[Lamassu] All clear'
}
body: '[Lamassu] All clear',
},
})
})
@ -97,8 +97,8 @@ describe('sendNoAlerts', () => {
expect(utils.sendNoAlerts(plugins, false, true)).toEqual({
email: {
body: 'No errors are reported for your machines.',
subject: '[Lamassu] All clear'
}
subject: '[Lamassu] All clear',
},
})
})
})

View file

@ -7,7 +7,7 @@ const {
CODES_DISPLAY,
NETWORK_DOWN_TIME,
PING,
ALERT_SEND_INTERVAL
ALERT_SEND_INTERVAL,
} = require('./codes')
const DETAIL_TEMPLATE = {
@ -20,16 +20,17 @@ const DETAIL_TEMPLATE = {
cryptoAddress: '',
direction: '',
fiat: '',
fiatCode: ''
fiatCode: '',
}
function parseEventNote (event) {
function parseEventNote(event) {
return _.set('note', JSON.parse(event.note), event)
}
function checkPing (device) {
const age = Date.now() - (new Date(device.lastPing).getTime())
if (age > NETWORK_DOWN_TIME) return [{ code: PING, age, machineName: device.name }]
function checkPing(device) {
const age = Date.now() - new Date(device.lastPing).getTime()
if (age > NETWORK_DOWN_TIME)
return [{ code: PING, age, machineName: device.name }]
return []
}
@ -41,7 +42,7 @@ const codeDisplay = code => CODES_DISPLAY[code]
const alertFingerprint = {
fingerprint: null,
lastAlertTime: null
lastAlertTime: null,
}
const getAlertFingerprint = () => alertFingerprint.fingerprint
@ -60,7 +61,7 @@ const shouldNotAlert = currentAlertFingerprint => {
)
}
function buildAlertFingerprint (alertRec, notifications) {
function buildAlertFingerprint(alertRec, notifications) {
const sms = getAlertTypes(alertRec, notifications.sms)
const email = getAlertTypes(alertRec, notifications.email)
if (sms.length === 0 && email.length === 0) return null
@ -72,7 +73,7 @@ function buildAlertFingerprint (alertRec, notifications) {
return crypto.createHash('sha256').update(subject).digest('hex')
}
function sendNoAlerts (plugins, smsEnabled, emailEnabled) {
function sendNoAlerts(plugins, smsEnabled, emailEnabled) {
const subject = '[Lamassu] All clear'
let rec = {}
@ -83,14 +84,20 @@ function sendNoAlerts (plugins, smsEnabled, emailEnabled) {
if (emailEnabled) {
rec = _.set(['email', 'subject'])(subject)(rec)
rec = _.set(['email', 'body'])('No errors are reported for your machines.')(
rec
rec,
)
}
return plugins.sendMessage(rec)
}
const buildTransactionMessage = (tx, rec, highValueTx, machineName, customer) => {
const buildTransactionMessage = (
tx,
rec,
highValueTx,
machineName,
customer,
) => {
const isCashOut = tx.direction === 'cashOut'
const direction = isCashOut ? 'Cash Out' : 'Cash In'
const crypto = `${coinUtils.toUnit(tx.cryptoAtoms, tx.cryptoCode)} ${
@ -124,41 +131,49 @@ const buildTransactionMessage = (tx, rec, highValueTx, machineName, customer) =>
const smsSubject = `A ${highValueTx ? 'high value ' : ''}${direction.toLowerCase()} transaction just happened at ${machineName} for ${fiat}`
const emailSubject = `A ${highValueTx ? 'high value ' : ''}transaction just happened`
return [{
sms: {
body: `${smsSubject} ${status}`
return [
{
sms: {
body: `${smsSubject} ${status}`,
},
email: {
emailSubject,
body,
},
webhook: {
topic: `New transaction`,
content: body,
},
},
email: {
emailSubject,
body
},
webhook: {
topic: `New transaction`,
content: body
}
}, highValueTx]
highValueTx,
]
}
function formatCurrency (num = 0, code) {
const formattedNumber = Number(num).toLocaleString(undefined, {maximumFractionDigits:2, minimumFractionDigits:2})
function formatCurrency(num = 0, code) {
const formattedNumber = Number(num).toLocaleString(undefined, {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})
return `${formattedNumber} ${code}`
}
function formatAge (age, settings) {
function formatAge(age, settings) {
return prettyMs(age, settings)
}
function buildDetail (obj) {
function buildDetail(obj) {
// obj validation
const objKeys = _.keys(obj)
const detailKeys = _.keys(DETAIL_TEMPLATE)
if ((_.difference(objKeys, detailKeys)).length > 0) {
return Promise.reject(new Error('Error when building detail object: invalid properties'))
if (_.difference(objKeys, detailKeys).length > 0) {
return Promise.reject(
new Error('Error when building detail object: invalid properties'),
)
}
return { ...DETAIL_TEMPLATE, ...obj }
}
function deviceAlerts (config, alertRec, device) {
function deviceAlerts(config, alertRec, device) {
let alerts = []
if (config.balance) {
alerts = _.concat(alerts, alertRec.devices[device].balanceAlerts)
@ -170,7 +185,7 @@ function deviceAlerts (config, alertRec, device) {
return alerts
}
function getAlertTypes (alertRec, config) {
function getAlertTypes(alertRec, config) {
let alerts = []
if (!isActive(config)) return alerts
@ -200,5 +215,5 @@ module.exports = {
formatCurrency,
formatAge,
buildDetail,
deviceAlerts
deviceAlerts,
}

View file

@ -12,10 +12,10 @@ const sendMessage = (settings, rec) => {
return axios({
method: 'POST',
url: WEBHOOK_URL,
data: body
data: body,
})
}
module.exports = {
sendMessage
sendMessage,
}