chore: server code formatting
This commit is contained in:
parent
aedabcbdee
commit
68517170e2
234 changed files with 9824 additions and 6195 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}]`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(', ')
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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' }])
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)',
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ const sendMessage = (settings, rec) => {
|
|||
return axios({
|
||||
method: 'POST',
|
||||
url: WEBHOOK_URL,
|
||||
data: body
|
||||
data: body,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendMessage
|
||||
sendMessage,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue