const _ = require('lodash/fp') const crypto = require('crypto') const { utils: coinUtils } = require('@lamassu/coins') const prettyMs = require('pretty-ms') const { CODES_DISPLAY, NETWORK_DOWN_TIME, PING, ALERT_SEND_INTERVAL, } = require('./codes') const DETAIL_TEMPLATE = { deviceId: '', cryptoCode: '', code: '', cassette: '', age: '', customerId: '', cryptoAddress: '', direction: '', fiat: '', fiatCode: '', } function parseEventNote(event) { return _.update( 'note', note => (typeof note === 'string' ? JSON.parse(note) : 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 }] return [] } const getDeviceTime = _.flow(_.get('device_time'), Date.parse) const isActive = it => it.active && (it.balance || it.errors) const codeDisplay = code => CODES_DISPLAY[code] const alertFingerprint = { fingerprint: null, lastAlertTime: null, } const getAlertFingerprint = () => alertFingerprint.fingerprint const getLastAlertTime = () => alertFingerprint.lastAlertTime const setAlertFingerprint = (fp, time = Date.now()) => { alertFingerprint.fingerprint = fp alertFingerprint.lastAlertTime = time } const shouldNotAlert = currentAlertFingerprint => { return ( currentAlertFingerprint === getAlertFingerprint() && getLastAlertTime() - Date.now() < ALERT_SEND_INTERVAL ) } 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 const smsTypes = _.map(codeDisplay, _.uniq(_.map('code', sms))).sort() const emailTypes = _.map(codeDisplay, _.uniq(_.map('code', email))).sort() const subject = _.concat(smsTypes, emailTypes).join(', ') return crypto.createHash('sha256').update(subject).digest('hex') } function sendNoAlerts(plugins, smsEnabled, emailEnabled) { const subject = '[Lamassu] All clear' let rec = {} if (smsEnabled) { rec = _.set(['sms', 'body'])(subject)(rec) } if (emailEnabled) { rec = _.set(['email', 'subject'])(subject)(rec) rec = _.set(['email', 'body'])('No errors are reported for your machines.')( rec, ) } return plugins.sendMessage(rec) } 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)} ${ tx.cryptoCode }` const fiat = `${tx.fiat} ${tx.fiatCode}` const customerName = customer.name || customer.id const phone = customer.phone ? `- Phone: ${customer.phone}` : '' let status = null if (rec.error) { status = `Error - ${rec.error}` } else { status = !isCashOut ? 'Successful' : !rec.isRedemption ? 'Successful & awaiting redemption' : 'Successful & dispensed' } const body = ` - Transaction ID: ${tx.id} - Status: ${status} - Machine name: ${machineName} - ${direction} - ${fiat} - ${crypto} - Customer: ${customerName} ${phone} ` 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}`, }, email: { emailSubject, body, }, webhook: { topic: `New transaction`, content: body, }, }, highValueTx, ] } function formatCurrency(num = 0, code) { const formattedNumber = Number(num).toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2, }) return `${formattedNumber} ${code}` } function formatAge(age, settings) { return prettyMs(age, settings) } 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'), ) } return { ...DETAIL_TEMPLATE, ...obj } } function deviceAlerts(config, alertRec, device) { let alerts = [] if (config.balance) { alerts = _.concat(alerts, alertRec.devices[device].balanceAlerts) } if (config.errors) { alerts = _.concat(alerts, alertRec.devices[device].deviceAlerts) } return alerts } function getAlertTypes(alertRec, config) { let alerts = [] if (!isActive(config)) return alerts if (config.balance) { alerts = _.concat(alerts, alertRec.general) } _.forEach(device => { alerts = _.concat(alerts, deviceAlerts(config, alertRec, device)) }, _.keys(alertRec.devices)) return alerts } module.exports = { codeDisplay, parseEventNote, getDeviceTime, checkPing, isActive, getAlertFingerprint, getLastAlertTime, setAlertFingerprint, shouldNotAlert, buildAlertFingerprint, sendNoAlerts, buildTransactionMessage, formatCurrency, formatAge, buildDetail, deviceAlerts, }