Feat: save highVolumeTxs on DB and plugins code refactor
Fix: remove unused module and add space before '('
Chore: add jest tests for transactionNotify
This commit is contained in:
parent
75bfb4b991
commit
2ced230020
8 changed files with 646 additions and 414 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const prettyMs = require('pretty-ms')
|
const prettyMs = require('pretty-ms')
|
||||||
|
|
||||||
|
const email = require('../email')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
PING,
|
PING,
|
||||||
STALE,
|
STALE,
|
||||||
|
|
@ -97,4 +100,7 @@ function emailAlert(alert) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { alertSubject, printEmailAlerts }
|
const sendMessage = email.sendMessage
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { alertSubject, printEmailAlerts, sendMessage }
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
const { STALE, STALE_STATE } = require('./codes')
|
|
||||||
|
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const queries = require('./queries')
|
|
||||||
|
const configManager = require('../new-config-manager')
|
||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
const machineLoader = require('../machine-loader')
|
||||||
|
const queries = require('./queries')
|
||||||
|
const settingsLoader = require('../new-settings-loader')
|
||||||
|
const customers = require('../customers')
|
||||||
|
|
||||||
const utils = require('./utils')
|
const utils = require('./utils')
|
||||||
const emailFuncs = require('./email')
|
const emailFuncs = require('./email')
|
||||||
const smsFuncs = require('./sms')
|
const smsFuncs = require('./sms')
|
||||||
|
const { STALE, STALE_STATE } = require('./codes')
|
||||||
|
|
||||||
function buildMessage(alerts, notifications) {
|
function buildMessage(alerts, notifications) {
|
||||||
const smsEnabled = utils.isActive(notifications.sms)
|
const smsEnabled = utils.isActive(notifications.sms)
|
||||||
|
|
@ -132,8 +136,79 @@ function checkStuckScreen(deviceEvents, machineName) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function transactionNotify (tx, rec) {
|
||||||
|
const settings = await settingsLoader.loadLatest()
|
||||||
|
const notifSettings = configManager.getGlobalNotifications(settings.config)
|
||||||
|
const highValueTx = tx.fiat.gt(notifSettings.highValueTransaction || Infinity)
|
||||||
|
const isCashOut = tx.direction === 'cashOut'
|
||||||
|
|
||||||
|
// high value tx on database
|
||||||
|
if(highValueTx) {
|
||||||
|
queries.addHighValueTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// alert through sms or email any transaction or high value transaction, if SMS || email alerts are enabled
|
||||||
|
const cashOutConfig = configManager.getCashOut(tx.deviceId, settings.config)
|
||||||
|
const zeroConfLimit = cashOutConfig.zeroConfLimit
|
||||||
|
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({})
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
machineLoader.getMachineName(tx.deviceId),
|
||||||
|
customerPromise
|
||||||
|
])
|
||||||
|
.then(([machineName, customer]) => {
|
||||||
|
return utils.buildTransactionMessage(tx, rec, highValueTx, machineName, customer)
|
||||||
|
})
|
||||||
|
.then(([msg, highValueTx]) => sendTransactionMessage(msg, highValueTx))
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendRedemptionMessage(txId, error) {
|
||||||
|
const subject = `Here's an update on transaction ${txId}`
|
||||||
|
const body = error
|
||||||
|
? `Error: ${error}`
|
||||||
|
: 'It was just dispensed successfully'
|
||||||
|
|
||||||
|
const rec = {
|
||||||
|
sms: {
|
||||||
|
body: `${subject} - ${body}`
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
subject,
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sendTransactionMessage(rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendTransactionMessage(rec, isHighValueTx) {
|
||||||
|
const settings = await settingsLoader.loadLatest()
|
||||||
|
const notifications = configManager.getGlobalNotifications(settings.config)
|
||||||
|
|
||||||
|
let promises = []
|
||||||
|
|
||||||
|
const emailActive =
|
||||||
|
notifications.email.active &&
|
||||||
|
(notifications.email.transactions || isHighValueTx)
|
||||||
|
if (emailActive) promises.push(emailFuncs.sendMessage(settings, rec))
|
||||||
|
|
||||||
|
const smsActive =
|
||||||
|
notifications.sms.active &&
|
||||||
|
(notifications.sms.transactions || isHighValueTx)
|
||||||
|
if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
transactionNotify,
|
||||||
checkNotification,
|
checkNotification,
|
||||||
checkPings,
|
checkPings,
|
||||||
checkStuckScreen
|
checkStuckScreen,
|
||||||
|
sendRedemptionMessage
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,12 @@
|
||||||
const dbm = require('../postgresql_interface')
|
const dbm = require('../postgresql_interface')
|
||||||
|
const db = require('../db')
|
||||||
|
const { v4: uuidv4 } = require('uuid')
|
||||||
|
|
||||||
module.exports = { machineEvents: dbm.machineEvents }
|
const addHighValueTx = (tx) => {
|
||||||
|
const sql = `INSERT INTO notifications (id, type, device_id, message, created) values($1, $2, $3, $4, CURRENT_TIMESTAMP)`
|
||||||
|
const direction = tx.direction === "cashOut" ? 'cash-out' : 'cash-in'
|
||||||
|
const message = `${tx.fiat} ${tx.fiatCode} ${direction} transaction`
|
||||||
|
return db.oneOrNone(sql, [uuidv4(), 'highValueTransaction', tx.deviceId, message])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { machineEvents: dbm.machineEvents, addHighValueTx }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const utils = require('./utils')
|
const utils = require('./utils')
|
||||||
|
const sms = require('../sms')
|
||||||
|
|
||||||
function printSmsAlerts(alertRec, config) {
|
function printSmsAlerts(alertRec, config) {
|
||||||
let alerts = []
|
let alerts = []
|
||||||
|
|
@ -50,4 +51,6 @@ function printSmsAlerts(alertRec, config) {
|
||||||
return '[Lamassu] Errors reported: ' + displayAlertTypes.join(', ')
|
return '[Lamassu] Errors reported: ' + displayAlertTypes.join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { printSmsAlerts }
|
const sendMessage = sms.sendMessage
|
||||||
|
|
||||||
|
module.exports = { printSmsAlerts, sendMessage }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,15 @@
|
||||||
|
const BigNumber = require('../../../lib/bn')
|
||||||
|
|
||||||
const notifier = require('..')
|
const notifier = require('..')
|
||||||
|
const utils = require('../utils')
|
||||||
|
const queries = require("../queries")
|
||||||
|
const emailFuncs = require('../email')
|
||||||
|
const smsFuncs = require('../sms')
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// https://stackoverflow.com/questions/58151010/difference-between-resetallmocks-resetmodules-resetmoduleregistry-restoreallm
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
// mock plugins object with mock data to test functions
|
// mock plugins object with mock data to test functions
|
||||||
const plugins = {
|
const plugins = {
|
||||||
|
|
@ -33,32 +44,57 @@ const plugins = {
|
||||||
checkBalances: () => []
|
checkBalances: () => []
|
||||||
}
|
}
|
||||||
|
|
||||||
const devices = [
|
const tx = {
|
||||||
{
|
id: 'bec8d452-9ea2-4846-841b-55a9df8bbd00',
|
||||||
deviceId:
|
deviceId:
|
||||||
'7e531a2666987aa27b9917ca17df7998f72771c57fdb21c90bc033999edd17e4',
|
'490ab16ee0c124512dc769be1f3e7ee3894ce1e5b4b8b975e134fb326e551e88',
|
||||||
lastPing: '2020-11-16T13:11:03.169Z',
|
toAddress: 'bc1q7s4yy5n9vp6zhlf6mrw3cttdgx5l3ysr2mhc4v',
|
||||||
name: 'Abc123'
|
cryptoAtoms: BigNumber(252100),
|
||||||
|
cryptoCode: 'BTC',
|
||||||
|
fiat: BigNumber(55),
|
||||||
|
fiatCode: 'USD',
|
||||||
|
fee: null,
|
||||||
|
txHash: null,
|
||||||
|
phone: null,
|
||||||
|
error: null,
|
||||||
|
created: '2020-12-04T16:28:11.016Z',
|
||||||
|
send: true,
|
||||||
|
sendConfirmed: false,
|
||||||
|
timedout: false,
|
||||||
|
sendTime: null,
|
||||||
|
errorCode: null,
|
||||||
|
operatorCompleted: false,
|
||||||
|
sendPending: true,
|
||||||
|
cashInFee: BigNumber(2),
|
||||||
|
cashInFeeCrypto: BigNumber(9500),
|
||||||
|
minimumTx: 5,
|
||||||
|
customerId: '47ac1184-8102-11e7-9079-8f13a7117867',
|
||||||
|
txVersion: 6,
|
||||||
|
termsAccepted: false,
|
||||||
|
commissionPercentage: BigNumber(0.11),
|
||||||
|
rawTickerPrice: BigNumber(18937.4),
|
||||||
|
isPaperWallet: false,
|
||||||
|
direction: 'cashIn'
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifSettings = {
|
||||||
|
email_active: false,
|
||||||
|
sms_active: true,
|
||||||
|
email_errors: false,
|
||||||
|
sms_errors: true,
|
||||||
|
sms_transactions: true,
|
||||||
|
highValueTransaction: Infinity, //this will make highValueTx always false
|
||||||
|
sms: {
|
||||||
|
active: true,
|
||||||
|
errors: true,
|
||||||
|
transactions: false // force early return
|
||||||
},
|
},
|
||||||
{
|
email: {
|
||||||
deviceId:
|
active: false,
|
||||||
'9871e58aa2643ff9445cbc299b50397430ada75157d6c29b4c93548fff0f48f7',
|
errors: false,
|
||||||
lastPing: '2020-11-16T16:21:35.948Z',
|
transactions: false // force early return
|
||||||
name: 'Machine 2'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceId:
|
|
||||||
'5ae0d02dedeb77b6521bd5eb7c9159bdc025873fa0bcb6f87aaddfbda0c50913',
|
|
||||||
lastPing: '2020-11-19T15:07:57.089Z',
|
|
||||||
name: 'Machine 3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deviceId:
|
|
||||||
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
|
||||||
lastPing: '2020-11-23T19:34:41.031Z',
|
|
||||||
name: 'New Machine 4 '
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
|
|
||||||
test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled', async () => {
|
test('Exits checkNotifications with Promise.resolve() if SMS and Email are disabled', async () => {
|
||||||
expect.assertions(1)
|
expect.assertions(1)
|
||||||
|
|
@ -116,12 +152,6 @@ test('Checkpings returns empty array as the value for the id prop, if the lastPi
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
// https://stackoverflow.com/questions/58151010/difference-between-resetallmocks-resetmodules-resetmoduleregistry-restoreallm
|
|
||||||
jest.restoreAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
const utils = require('../utils')
|
|
||||||
|
|
||||||
test('Check notification resolves to undefined if shouldNotAlert is called and is true', async () => {
|
test('Check notification resolves to undefined if shouldNotAlert is called and is true', async () => {
|
||||||
const mockShouldNotAlert = jest.spyOn(utils, 'shouldNotAlert')
|
const mockShouldNotAlert = jest.spyOn(utils, 'shouldNotAlert')
|
||||||
|
|
@ -160,7 +190,6 @@ test('If no alert fingerprint and inAlert is true, exits on call to sendNoAlerts
|
||||||
expect(mockSendNoAlerts).toHaveBeenCalledTimes(1)
|
expect(mockSendNoAlerts).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// vvv tests for checkstuckscreen...
|
// vvv tests for checkstuckscreen...
|
||||||
test('checkStuckScreen returns [] when no events are found', () => {
|
test('checkStuckScreen returns [] when no events are found', () => {
|
||||||
expect(notifier.checkStuckScreen([], 'Abc123')).toEqual([])
|
expect(notifier.checkStuckScreen([], 'Abc123')).toEqual([])
|
||||||
|
|
@ -168,67 +197,141 @@ test('checkStuckScreen returns [] when no events are found', () => {
|
||||||
|
|
||||||
test('checkStuckScreen returns [] if most recent event is idle', () => {
|
test('checkStuckScreen returns [] if most recent event is idle', () => {
|
||||||
// device_time is what matters for the sorting of the events by recency
|
// device_time is what matters for the sorting of the events by recency
|
||||||
expect(notifier.checkStuckScreen([{
|
expect(
|
||||||
|
notifier.checkStuckScreen([
|
||||||
|
{
|
||||||
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
||||||
device_id: 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
device_id:
|
||||||
|
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
||||||
event_type: 'stateChange',
|
event_type: 'stateChange',
|
||||||
note: '{"state":"chooseCoin","isIdle":false}',
|
note: '{"state":"chooseCoin","isIdle":false}',
|
||||||
created: "2020-11-23T19:30:29.209Z",
|
created: '2020-11-23T19:30:29.209Z',
|
||||||
device_time: "1999-11-23T19:30:29.177Z",
|
device_time: '1999-11-23T19:30:29.177Z',
|
||||||
age: 157352628.123
|
age: 157352628.123
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
||||||
device_id: 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
device_id:
|
||||||
|
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
||||||
event_type: 'stateChange',
|
event_type: 'stateChange',
|
||||||
note: '{"state":"chooseCoin","isIdle":true}',
|
note: '{"state":"chooseCoin","isIdle":true}',
|
||||||
created: "2020-11-23T19:30:29.209Z",
|
created: '2020-11-23T19:30:29.209Z',
|
||||||
device_time: "2020-11-23T19:30:29.177Z",
|
device_time: '2020-11-23T19:30:29.177Z',
|
||||||
age: 157352628.123
|
age: 157352628.123
|
||||||
}])).toEqual([])
|
}
|
||||||
|
])
|
||||||
|
).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('checkStuckScreen returns object array of length 1 with prop code: "STALE" if age > STALE_STATE', () => {
|
test('checkStuckScreen returns object array of length 1 with prop code: "STALE" if age > STALE_STATE', () => {
|
||||||
// there is an age 0 and an isIdle true in the first object but it will be below the second one in the sorting order and thus ignored
|
// there is an age 0 and an isIdle true in the first object but it will be below the second one in the sorting order and thus ignored
|
||||||
const result = notifier.checkStuckScreen([{
|
const result = notifier.checkStuckScreen([
|
||||||
|
{
|
||||||
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
||||||
device_id: 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
device_id:
|
||||||
|
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
||||||
event_type: 'stateChange',
|
event_type: 'stateChange',
|
||||||
note: '{"state":"chooseCoin","isIdle":true}',
|
note: '{"state":"chooseCoin","isIdle":true}',
|
||||||
created: "2020-11-23T19:30:29.209Z",
|
created: '2020-11-23T19:30:29.209Z',
|
||||||
device_time: "1999-11-23T19:30:29.177Z",
|
device_time: '1999-11-23T19:30:29.177Z',
|
||||||
age: 0
|
age: 0
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
||||||
device_id: 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
device_id:
|
||||||
|
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
||||||
event_type: 'stateChange',
|
event_type: 'stateChange',
|
||||||
note: '{"state":"chooseCoin","isIdle":false}',
|
note: '{"state":"chooseCoin","isIdle":false}',
|
||||||
created: "2020-11-23T19:30:29.209Z",
|
created: '2020-11-23T19:30:29.209Z',
|
||||||
device_time: "2020-11-23T19:30:29.177Z",
|
device_time: '2020-11-23T19:30:29.177Z',
|
||||||
age: 157352628.123
|
age: 157352628.123
|
||||||
}])
|
}
|
||||||
expect(result[0]).toMatchObject({code: "STALE"})
|
])
|
||||||
|
expect(result[0]).toMatchObject({ code: 'STALE' })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
|
test('checkStuckScreen returns empty array if age < STALE_STATE', () => {
|
||||||
const STALE_STATE = require("../codes").STALE_STATE
|
const STALE_STATE = require('../codes').STALE_STATE
|
||||||
const result1 = notifier.checkStuckScreen([{
|
const result1 = notifier.checkStuckScreen([
|
||||||
|
{
|
||||||
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
||||||
device_id: 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
device_id:
|
||||||
|
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
||||||
event_type: 'stateChange',
|
event_type: 'stateChange',
|
||||||
note: '{"state":"chooseCoin","isIdle":false}',
|
note: '{"state":"chooseCoin","isIdle":false}',
|
||||||
created: "2020-11-23T19:30:29.209Z",
|
created: '2020-11-23T19:30:29.209Z',
|
||||||
device_time: "2020-11-23T19:30:29.177Z",
|
device_time: '2020-11-23T19:30:29.177Z',
|
||||||
age: 0
|
age: 0
|
||||||
}])
|
}
|
||||||
const result2 = notifier.checkStuckScreen([{
|
])
|
||||||
|
const result2 = notifier.checkStuckScreen([
|
||||||
|
{
|
||||||
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
id: '48ae51c6-c5b4-485e-b81d-aa337fc025e2',
|
||||||
device_id: 'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
device_id:
|
||||||
|
'f02af604ca9010bd9ae04c427a24da90130da10d355f0a9b235886a89008fc05',
|
||||||
event_type: 'stateChange',
|
event_type: 'stateChange',
|
||||||
note: '{"state":"chooseCoin","isIdle":false}',
|
note: '{"state":"chooseCoin","isIdle":false}',
|
||||||
created: "2020-11-23T19:30:29.209Z",
|
created: '2020-11-23T19:30:29.209Z',
|
||||||
device_time: "2020-11-23T19:30:29.177Z",
|
device_time: '2020-11-23T19:30:29.177Z',
|
||||||
age: STALE_STATE
|
age: STALE_STATE
|
||||||
}])
|
}
|
||||||
|
])
|
||||||
expect(result1).toEqual([])
|
expect(result1).toEqual([])
|
||||||
expect(result2).toEqual([])
|
expect(result2).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("calls sendRedemptionMessage if !zeroConf and rec.isRedemption", async () => {
|
||||||
|
const configManager = require('../../new-config-manager')
|
||||||
|
const settingsLoader = require('../../new-settings-loader')
|
||||||
|
|
||||||
|
const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
|
||||||
|
const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
|
||||||
|
const getCashOut = jest.spyOn(configManager, 'getCashOut')
|
||||||
|
|
||||||
|
// sendRedemptionMessage will cause this func to be called
|
||||||
|
jest.spyOn(smsFuncs, 'sendMessage').mockImplementation((_, rec) => rec)
|
||||||
|
|
||||||
|
getCashOut.mockReturnValue({zeroConfLimit: -Infinity})
|
||||||
|
loadLatest.mockReturnValue({})
|
||||||
|
getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: 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"
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
subject: "Here's an update on transaction bec8d452-9ea2-4846-841b-55a9df8bbd00",
|
||||||
|
body: 'It was just dispensed successfully'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("calls sendTransactionMessage if !zeroConf and !rec.isRedemption", async () => {
|
||||||
|
const configManager = require('../../new-config-manager')
|
||||||
|
const settingsLoader = require('../../new-settings-loader')
|
||||||
|
const machineLoader = require('../../machine-loader')
|
||||||
|
|
||||||
|
const loadLatest = jest.spyOn(settingsLoader, 'loadLatest')
|
||||||
|
const getGlobalNotifications = jest.spyOn(configManager, 'getGlobalNotifications')
|
||||||
|
const getCashOut = jest.spyOn(configManager, 'getCashOut')
|
||||||
|
const getMachineName = jest.spyOn(machineLoader, '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}))
|
||||||
|
buildTransactionMessage.mockImplementation(() => ["mock message", false])
|
||||||
|
|
||||||
|
getMachineName.mockReturnValue("mockMachineName")
|
||||||
|
getCashOut.mockReturnValue({zeroConfLimit: -Infinity})
|
||||||
|
loadLatest.mockReturnValue({})
|
||||||
|
getGlobalNotifications.mockReturnValue({... notifSettings, sms: { active: true, errors: true, transactions: 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'}])
|
||||||
|
})
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
const coinUtils = require('../coin-utils')
|
||||||
const {
|
const {
|
||||||
CODES_DISPLAY,
|
CODES_DISPLAY,
|
||||||
NETWORK_DOWN_TIME,
|
NETWORK_DOWN_TIME,
|
||||||
|
|
@ -96,6 +98,51 @@ function sendNoAlerts(plugins, smsEnabled, emailEnabled) {
|
||||||
return plugins.sendMessage(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
|
||||||
|
}
|
||||||
|
}, highValueTx]
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
codeDisplay,
|
codeDisplay,
|
||||||
parseEventNote,
|
parseEventNote,
|
||||||
|
|
@ -107,5 +154,6 @@ module.exports = {
|
||||||
setAlertFingerprint,
|
setAlertFingerprint,
|
||||||
shouldNotAlert,
|
shouldNotAlert,
|
||||||
buildAlertFingerprint,
|
buildAlertFingerprint,
|
||||||
sendNoAlerts
|
sendNoAlerts,
|
||||||
|
buildTransactionMessage
|
||||||
}
|
}
|
||||||
|
|
|
||||||
338
lib/plugins.js
338
lib/plugins.js
|
|
@ -1,4 +1,3 @@
|
||||||
const uuid = require('uuid')
|
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
const argv = require('minimist')(process.argv.slice(2))
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
|
@ -24,6 +23,8 @@ const coinUtils = require('./coin-utils')
|
||||||
const commissionMath = require('./commission-math')
|
const commissionMath = require('./commission-math')
|
||||||
const promoCodes = require('./promo-codes')
|
const promoCodes = require('./promo-codes')
|
||||||
|
|
||||||
|
const notifier = require('./notifier/index')
|
||||||
|
|
||||||
const mapValuesWithKey = _.mapValues.convert({
|
const mapValuesWithKey = _.mapValues.convert({
|
||||||
cap: false
|
cap: false
|
||||||
})
|
})
|
||||||
|
|
@ -54,7 +55,8 @@ function plugins (settings, deviceId) {
|
||||||
? undefined
|
? undefined
|
||||||
: BN(1).add(BN(commissions.cashOut).div(100))
|
: BN(1).add(BN(commissions.cashOut).div(100))
|
||||||
|
|
||||||
if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode)
|
if (Date.now() - rateRec.timestamp > STALE_TICKER)
|
||||||
|
return logger.warn('Stale rate for ' + cryptoCode)
|
||||||
const rate = rateRec.rates
|
const rate = rateRec.rates
|
||||||
|
|
||||||
withCommission ? rates[cryptoCode] = {
|
withCommission ? rates[cryptoCode] = {
|
||||||
|
|
@ -88,8 +90,10 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
cryptoCodes.forEach((cryptoCode, i) => {
|
cryptoCodes.forEach((cryptoCode, i) => {
|
||||||
const balanceRec = balanceRecs[i]
|
const balanceRec = balanceRecs[i]
|
||||||
if (!balanceRec) return logger.warn('No balance for ' + cryptoCode + ' yet')
|
if (!balanceRec)
|
||||||
if (Date.now() - balanceRec.timestamp > STALE_BALANCE) return logger.warn('Stale balance for ' + cryptoCode)
|
return logger.warn('No balance for ' + cryptoCode + ' yet')
|
||||||
|
if (Date.now() - balanceRec.timestamp > STALE_BALANCE)
|
||||||
|
return logger.warn('Stale balance for ' + cryptoCode)
|
||||||
|
|
||||||
balances[cryptoCode] = balanceRec.balance
|
balances[cryptoCode] = balanceRec.balance
|
||||||
})
|
})
|
||||||
|
|
@ -109,10 +113,13 @@ function plugins (settings, deviceId) {
|
||||||
const sumTxs = (sum, tx) => {
|
const sumTxs = (sum, tx) => {
|
||||||
const bills = tx.bills
|
const bills = tx.bills
|
||||||
const sameDenominations = a => a[0].denomination === a[1].denomination
|
const sameDenominations = a => a[0].denomination === a[1].denomination
|
||||||
const doDenominationsMatch = _.every(sameDenominations, _.zip(cassettes, bills))
|
const doDenominationsMatch = _.every(
|
||||||
|
sameDenominations,
|
||||||
|
_.zip(cassettes, bills)
|
||||||
|
)
|
||||||
|
|
||||||
if (!doDenominationsMatch) {
|
if (!doDenominationsMatch) {
|
||||||
throw new Error('Denominations don\'t add up, cassettes were changed.')
|
throw new Error("Denominations don't add up, cassettes were changed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills))
|
return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills))
|
||||||
|
|
@ -155,6 +162,17 @@ function plugins (settings, deviceId) {
|
||||||
? argv.cassettes.split(',')
|
? argv.cassettes.split(',')
|
||||||
: rec.counts
|
: rec.counts
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
dbm.cassetteCounts(deviceId),
|
||||||
|
cashOutHelper.redeemableTxs(deviceId, excludeTxId)
|
||||||
|
]).then(([rec, _redeemableTxs]) => {
|
||||||
|
const redeemableTxs = _.reject(
|
||||||
|
_.matchesProperty('id', excludeTxId),
|
||||||
|
_redeemableTxs
|
||||||
|
)
|
||||||
|
|
||||||
|
const counts = argv.cassettes ? argv.cassettes.split(',') : rec.counts
|
||||||
|
|
||||||
const cassettes = [
|
const cassettes = [
|
||||||
{
|
{
|
||||||
denomination: parseInt(denominations[0], 10),
|
denomination: parseInt(denominations[0], 10),
|
||||||
|
|
@ -188,18 +206,23 @@ function plugins (settings, deviceId) {
|
||||||
order by id desc
|
order by id desc
|
||||||
limit 1`
|
limit 1`
|
||||||
|
|
||||||
return db.one(sql, ['config'])
|
return db.one(sql, ['config']).then(row => row.id)
|
||||||
.then(row => row.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapCoinSettings (coinParams) {
|
function mapCoinSettings (coinParams) {
|
||||||
const cryptoCode = coinParams[0]
|
const cryptoCode = coinParams[0]
|
||||||
const cryptoNetwork = coinParams[1]
|
const cryptoNetwork = coinParams[1]
|
||||||
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
|
const commissions = configManager.getCommissions(
|
||||||
|
cryptoCode,
|
||||||
|
deviceId,
|
||||||
|
settings.config
|
||||||
|
)
|
||||||
const minimumTx = BN(commissions.minimumTx)
|
const minimumTx = BN(commissions.minimumTx)
|
||||||
const cashInFee = BN(commissions.fixedFee)
|
const cashInFee = BN(commissions.fixedFee)
|
||||||
const cashInCommission = BN(commissions.cashIn)
|
const cashInCommission = BN(commissions.cashIn)
|
||||||
const cashOutCommission = _.isNumber(commissions.cashOut) ? BN(commissions.cashOut) : null
|
const cashOutCommission = _.isNumber(commissions.cashOut)
|
||||||
|
? BN(commissions.cashOut)
|
||||||
|
: null
|
||||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -213,15 +236,25 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pollQueries (serialNumber, deviceTime, deviceRec, machineVersion, machineModel) {
|
function pollQueries (
|
||||||
|
serialNumber,
|
||||||
|
deviceTime,
|
||||||
|
deviceRec,
|
||||||
|
machineVersion,
|
||||||
|
machineModel
|
||||||
|
) {
|
||||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
||||||
|
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
const fiatCode = localeConfig.fiatCurrency
|
||||||
const cryptoCodes = localeConfig.cryptoCurrencies
|
const cryptoCodes = localeConfig.cryptoCurrencies
|
||||||
|
|
||||||
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c))
|
const tickerPromises = cryptoCodes.map(c =>
|
||||||
|
ticker.getRates(settings, fiatCode, c)
|
||||||
|
)
|
||||||
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
|
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
|
||||||
const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c))
|
const testnetPromises = cryptoCodes.map(c =>
|
||||||
|
wallet.cryptoNetwork(settings, c)
|
||||||
|
)
|
||||||
const pingPromise = recordPing(deviceTime, machineVersion, machineModel)
|
const pingPromise = recordPing(deviceTime, machineVersion, machineModel)
|
||||||
const currentConfigVersionPromise = fetchCurrentConfigVersion()
|
const currentConfigVersionPromise = fetchCurrentConfigVersion()
|
||||||
const currentAvailablePromoCodes = promoCodes.getNumberOfAvailablePromoCodes()
|
const currentAvailablePromoCodes = promoCodes.getNumberOfAvailablePromoCodes()
|
||||||
|
|
@ -256,7 +289,12 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendCoins (tx) {
|
function sendCoins (tx) {
|
||||||
return wallet.sendCoins(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)
|
return wallet.sendCoins(
|
||||||
|
settings,
|
||||||
|
tx.toAddress,
|
||||||
|
tx.cryptoAtoms,
|
||||||
|
tx.cryptoCode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordPing (deviceTime, version, model) {
|
function recordPing (deviceTime, version, model) {
|
||||||
|
|
@ -267,11 +305,18 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
db.none(`insert into machine_pings(device_id, device_time) values($1, $2)
|
db.none(
|
||||||
ON CONFLICT (device_id) DO UPDATE SET device_time = $2, updated = now()`, [deviceId, deviceTime]),
|
`insert into machine_pings(device_id, device_time) values($1, $2)
|
||||||
db.none(pgp.helpers.update(devices, null, 'devices') + 'WHERE device_id = ${deviceId}', {
|
ON CONFLICT (device_id) DO UPDATE SET device_time = $2, updated = now()`,
|
||||||
|
[deviceId, deviceTime]
|
||||||
|
),
|
||||||
|
db.none(
|
||||||
|
pgp.helpers.update(devices, null, 'devices') +
|
||||||
|
'WHERE device_id = ${deviceId}',
|
||||||
|
{
|
||||||
deviceId
|
deviceId
|
||||||
})
|
}
|
||||||
|
)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,12 +348,15 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fiatBalance (fiatCode, cryptoCode) {
|
function fiatBalance (fiatCode, cryptoCode) {
|
||||||
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
|
const commissions = configManager.getCommissions(
|
||||||
|
cryptoCode,
|
||||||
|
deviceId,
|
||||||
|
settings.config
|
||||||
|
)
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
ticker.getRates(settings, fiatCode, cryptoCode),
|
ticker.getRates(settings, fiatCode, cryptoCode),
|
||||||
wallet.balance(settings, cryptoCode)
|
wallet.balance(settings, cryptoCode)
|
||||||
])
|
]).then(([rates, balanceRec]) => {
|
||||||
.then(([rates, balanceRec]) => {
|
|
||||||
if (!rates || !balanceRec) return null
|
if (!rates || !balanceRec) return null
|
||||||
|
|
||||||
const rawRate = rates.rates.ask
|
const rawRate = rates.rates.ask
|
||||||
|
|
@ -345,8 +393,7 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sms.sendMessage(settings, rec)
|
return sms.sendMessage(settings, rec).then(() => {
|
||||||
.then(() => {
|
|
||||||
const sql = 'update cash_out_txs set notified=$1 where id=$2'
|
const sql = 'update cash_out_txs set notified=$1 where id=$2'
|
||||||
const values = [true, tx.id]
|
const values = [true, tx.id]
|
||||||
|
|
||||||
|
|
@ -355,90 +402,22 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyOperator (tx, rec) {
|
function notifyOperator (tx, rec) {
|
||||||
const notifications = configManager.getGlobalNotifications(settings.config)
|
// notify operator about new transaction and add high volume txs to database
|
||||||
|
return notifier.transactionNotify(tx, rec)
|
||||||
const notificationsEnabled = notifications.sms.transactions || notifications.email.transactions
|
|
||||||
const highValueTx = tx.fiat.gt(notifications.highValueTransaction || Infinity)
|
|
||||||
|
|
||||||
if (!notificationsEnabled && !highValueTx) return Promise.resolve()
|
|
||||||
|
|
||||||
const isCashOut = tx.direction === 'cashOut'
|
|
||||||
const zeroConf = isCashOut && isZeroConf(tx)
|
|
||||||
|
|
||||||
// 0-conf cash-out should only send notification on redemption
|
|
||||||
if (zeroConf && isCashOut && !rec.isRedemption && !rec.error) return Promise.resolve()
|
|
||||||
|
|
||||||
if (!zeroConf && rec.isRedemption) return sendRedemptionMessage(tx.id, rec.error)
|
|
||||||
|
|
||||||
const customerPromise = tx.customerId ? customers.getById(tx.customerId) : Promise.resolve({})
|
|
||||||
|
|
||||||
return Promise.all([machineLoader.getMachineName(tx.deviceId), customerPromise])
|
|
||||||
.then(([machineName, customer]) => {
|
|
||||||
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
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}, highValueTx]
|
|
||||||
})
|
|
||||||
.then(([rec, highValueTx]) => sendTransactionMessage(rec, highValueTx))
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendRedemptionMessage (txId, error) {
|
|
||||||
const subject = `Here's an update on transaction ${txId}`
|
|
||||||
const body = error ? `Error: ${error}` : 'It was just dispensed successfully'
|
|
||||||
|
|
||||||
const rec = {
|
|
||||||
sms: {
|
|
||||||
body: `${subject} - ${body}`
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
subject,
|
|
||||||
body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sendTransactionMessage(rec)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearOldLogs () {
|
function clearOldLogs () {
|
||||||
return logs.clearOldLogs()
|
return logs.clearOldLogs().catch(logger.error)
|
||||||
.catch(logger.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pong () {
|
function pong () {
|
||||||
return db.none(`UPDATE server_events SET created=now() WHERE event_type=$1;
|
return db
|
||||||
|
.none(
|
||||||
|
`UPDATE server_events SET created=now() WHERE event_type=$1;
|
||||||
INSERT INTO server_events (event_type) SELECT $1
|
INSERT INTO server_events (event_type) SELECT $1
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM server_events WHERE event_type=$1);`, ['ping'])
|
WHERE NOT EXISTS (SELECT 1 FROM server_events WHERE event_type=$1);`,
|
||||||
|
['ping']
|
||||||
|
)
|
||||||
.catch(logger.error)
|
.catch(logger.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,13 +458,16 @@ function plugins (settings, deviceId) {
|
||||||
const marketTradesQueues = tradesQueues[market]
|
const marketTradesQueues = tradesQueues[market]
|
||||||
if (!marketTradesQueues || marketTradesQueues.length === 0) return null
|
if (!marketTradesQueues || marketTradesQueues.length === 0) return null
|
||||||
|
|
||||||
logger.debug('[%s] tradesQueues size: %d', market, marketTradesQueues.length)
|
logger.debug(
|
||||||
|
'[%s] tradesQueues size: %d',
|
||||||
|
market,
|
||||||
|
marketTradesQueues.length
|
||||||
|
)
|
||||||
logger.debug('[%s] tradesQueues head: %j', market, marketTradesQueues[0])
|
logger.debug('[%s] tradesQueues head: %j', market, marketTradesQueues[0])
|
||||||
|
|
||||||
const t1 = Date.now()
|
const t1 = Date.now()
|
||||||
|
|
||||||
const filtered = marketTradesQueues
|
const filtered = marketTradesQueues.filter(tradeEntry => {
|
||||||
.filter(tradeEntry => {
|
|
||||||
return t1 - tradeEntry.timestamp < TRADE_TTL
|
return t1 - tradeEntry.timestamp < TRADE_TTL
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -498,10 +480,14 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
if (filtered.length === 0) return null
|
if (filtered.length === 0) return null
|
||||||
|
|
||||||
const cryptoAtoms = filtered
|
const cryptoAtoms = filtered.reduce(
|
||||||
.reduce((prev, current) => prev.plus(current.cryptoAtoms), BN(0))
|
(prev, current) => prev.plus(current.cryptoAtoms),
|
||||||
|
BN(0)
|
||||||
|
)
|
||||||
|
|
||||||
const timestamp = filtered.map(r => r.timestamp).reduce((acc, r) => Math.max(acc, r), 0)
|
const timestamp = filtered
|
||||||
|
.map(r => r.timestamp)
|
||||||
|
.reduce((acc, r) => Math.max(acc, r), 0)
|
||||||
|
|
||||||
const consolidatedTrade = {
|
const consolidatedTrade = {
|
||||||
fiatCode,
|
fiatCode,
|
||||||
|
|
@ -517,11 +503,15 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeTrades () {
|
function executeTrades () {
|
||||||
return machineLoader.getMachines()
|
return machineLoader
|
||||||
|
.getMachines()
|
||||||
.then(devices => {
|
.then(devices => {
|
||||||
const deviceIds = devices.map(device => device.deviceId)
|
const deviceIds = devices.map(device => device.deviceId)
|
||||||
const lists = deviceIds.map(deviceId => {
|
const lists = deviceIds.map(deviceId => {
|
||||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
const localeConfig = configManager.getLocale(
|
||||||
|
deviceId,
|
||||||
|
settings.config
|
||||||
|
)
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
const fiatCode = localeConfig.fiatCurrency
|
||||||
const cryptoCodes = localeConfig.cryptoCurrencies
|
const cryptoCodes = localeConfig.cryptoCurrencies
|
||||||
|
|
||||||
|
|
@ -531,8 +521,9 @@ function plugins (settings, deviceId) {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tradesPromises = _.uniq(_.flatten(lists))
|
const tradesPromises = _.uniq(_.flatten(lists)).map(r =>
|
||||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
|
executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)
|
||||||
|
)
|
||||||
|
|
||||||
return Promise.all(tradesPromises)
|
return Promise.all(tradesPromises)
|
||||||
})
|
})
|
||||||
|
|
@ -547,8 +538,7 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
|
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
|
||||||
|
|
||||||
return executeTradeForType(tradeEntry)
|
return executeTradeForType(tradeEntry).catch(err => {
|
||||||
.catch(err => {
|
|
||||||
tradesQueues[market].push(tradeEntry)
|
tradesQueues[market].push(tradeEntry)
|
||||||
if (err.name === 'orderTooSmall') return logger.debug(err.message)
|
if (err.name === 'orderTooSmall') return logger.debug(err.message)
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
|
|
@ -556,7 +546,8 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeTradeForType (_tradeEntry) {
|
function executeTradeForType (_tradeEntry) {
|
||||||
const expand = te => _.assign(te, {
|
const expand = te =>
|
||||||
|
_.assign(te, {
|
||||||
cryptoAtoms: te.cryptoAtoms.abs(),
|
cryptoAtoms: te.cryptoAtoms.abs(),
|
||||||
type: te.cryptoAtoms.gte(0) ? 'buy' : 'sell'
|
type: te.cryptoAtoms.gte(0) ? 'buy' : 'sell'
|
||||||
})
|
})
|
||||||
|
|
@ -564,24 +555,26 @@ function plugins (settings, deviceId) {
|
||||||
const tradeEntry = expand(_tradeEntry)
|
const tradeEntry = expand(_tradeEntry)
|
||||||
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
|
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
|
||||||
|
|
||||||
return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode)
|
return execute(
|
||||||
|
settings,
|
||||||
|
tradeEntry.cryptoAtoms,
|
||||||
|
tradeEntry.fiatCode,
|
||||||
|
tradeEntry.cryptoCode
|
||||||
|
)
|
||||||
.then(() => recordTrade(tradeEntry))
|
.then(() => recordTrade(tradeEntry))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
return recordTrade(tradeEntry, err)
|
return recordTrade(tradeEntry, err).then(() => {
|
||||||
.then(() => {
|
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertBigNumFields (obj) {
|
function convertBigNumFields (obj) {
|
||||||
const convert = (value, key) => _.includes(key, ['cryptoAtoms', 'fiat'])
|
const convert = (value, key) =>
|
||||||
? value.toString()
|
_.includes(key, ['cryptoAtoms', 'fiat']) ? value.toString() : value
|
||||||
: value
|
|
||||||
|
|
||||||
const convertKey = key => _.includes(key, ['cryptoAtoms', 'fiat'])
|
const convertKey = key =>
|
||||||
? key + '#'
|
_.includes(key, ['cryptoAtoms', 'fiat']) ? key + '#' : key
|
||||||
: key
|
|
||||||
|
|
||||||
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
|
return _.mapKeys(convertKey, mapValuesWithKey(convert, obj))
|
||||||
}
|
}
|
||||||
|
|
@ -611,22 +604,10 @@ function plugins (settings, deviceId) {
|
||||||
const notifications = configManager.getGlobalNotifications(settings.config)
|
const notifications = configManager.getGlobalNotifications(settings.config)
|
||||||
|
|
||||||
let promises = []
|
let promises = []
|
||||||
if (notifications.email.active && rec.email) promises.push(email.sendMessage(settings, rec))
|
if (notifications.email.active && rec.email)
|
||||||
if (notifications.sms.active && rec.sms) promises.push(sms.sendMessage(settings, rec))
|
promises.push(email.sendMessage(settings, rec))
|
||||||
|
if (notifications.sms.active && rec.sms)
|
||||||
return Promise.all(promises)
|
promises.push(sms.sendMessage(settings, rec))
|
||||||
}
|
|
||||||
|
|
||||||
function sendTransactionMessage (rec, isHighValueTx) {
|
|
||||||
const notifications = configManager.getGlobalNotifications(settings.config)
|
|
||||||
|
|
||||||
let promises = []
|
|
||||||
|
|
||||||
const emailActive = notifications.email.active && (notifications.email.transactions || isHighValueTx)
|
|
||||||
if (emailActive) promises.push(email.sendMessage(settings, rec))
|
|
||||||
|
|
||||||
const smsActive = notifications.sms.active && (notifications.sms.transactions || isHighValueTx)
|
|
||||||
if (smsActive) promises.push(sms.sendMessage(settings, rec))
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
}
|
}
|
||||||
|
|
@ -636,16 +617,24 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDeviceCashBalances (fiatCode, device) {
|
function checkDeviceCashBalances (fiatCode, device) {
|
||||||
const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config)
|
const cashOutConfig = configManager.getCashOut(
|
||||||
|
device.deviceId,
|
||||||
|
settings.config
|
||||||
|
)
|
||||||
const denomination1 = cashOutConfig.top
|
const denomination1 = cashOutConfig.top
|
||||||
const denomination2 = cashOutConfig.bottom
|
const denomination2 = cashOutConfig.bottom
|
||||||
const cashOutEnabled = cashOutConfig.active
|
const cashOutEnabled = cashOutConfig.active
|
||||||
|
|
||||||
const notifications = configManager.getNotifications(null, device.deviceId, settings.config)
|
const notifications = configManager.getNotifications(
|
||||||
|
null,
|
||||||
|
device.deviceId,
|
||||||
|
settings.config
|
||||||
|
)
|
||||||
|
|
||||||
const machineName = device.name
|
const machineName = device.name
|
||||||
|
|
||||||
const cashInAlert = device.cashbox > notifications.cashInAlertThreshold
|
const cashInAlert =
|
||||||
|
device.cashbox > notifications.cashInAlertThreshold
|
||||||
? {
|
? {
|
||||||
code: 'CASH_BOX_FULL',
|
code: 'CASH_BOX_FULL',
|
||||||
machineName,
|
machineName,
|
||||||
|
|
@ -654,7 +643,8 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const cassette1Alert = cashOutEnabled && device.cassette1 < notifications.fiatBalanceCassette1
|
const cassette1Alert =
|
||||||
|
cashOutEnabled && device.cassette1 < notifications.fiatBalanceCassette1
|
||||||
? {
|
? {
|
||||||
code: 'LOW_CASH_OUT',
|
code: 'LOW_CASH_OUT',
|
||||||
cassette: 1,
|
cassette: 1,
|
||||||
|
|
@ -666,7 +656,8 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const cassette2Alert = cashOutEnabled && device.cassette2 < notifications.fiatBalanceCassette2
|
const cassette2Alert =
|
||||||
|
cashOutEnabled && device.cassette2 < notifications.fiatBalanceCassette2
|
||||||
? {
|
? {
|
||||||
code: 'LOW_CASH_OUT',
|
code: 'LOW_CASH_OUT',
|
||||||
cassette: 2,
|
cassette: 2,
|
||||||
|
|
@ -682,7 +673,8 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCryptoBalances (fiatCode, devices) {
|
function checkCryptoBalances (fiatCode, devices) {
|
||||||
const fiatBalancePromises = cryptoCodes => _.map(c => fiatBalance(fiatCode, c), cryptoCodes)
|
const fiatBalancePromises = cryptoCodes =>
|
||||||
|
_.map(c => fiatBalance(fiatCode, c), cryptoCodes)
|
||||||
|
|
||||||
const fetchCryptoCodes = _deviceId => {
|
const fetchCryptoCodes = _deviceId => {
|
||||||
const localeConfig = configManager.getLocale(_deviceId, settings.config)
|
const localeConfig = configManager.getLocale(_deviceId, settings.config)
|
||||||
|
|
@ -693,8 +685,9 @@ function plugins (settings, deviceId) {
|
||||||
const cryptoCodes = union(devices)
|
const cryptoCodes = union(devices)
|
||||||
const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode])
|
const checkCryptoBalanceWithFiat = _.partial(checkCryptoBalance, [fiatCode])
|
||||||
|
|
||||||
return Promise.all(fiatBalancePromises(cryptoCodes))
|
return Promise.all(fiatBalancePromises(cryptoCodes)).then(balances =>
|
||||||
.then(balances => _.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances)))
|
_.map(checkCryptoBalanceWithFiat, _.zip(cryptoCodes, balances))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCryptoBalance (fiatCode, rec) {
|
function checkCryptoBalance (fiatCode, rec) {
|
||||||
|
|
@ -702,20 +695,30 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
if (!fiatBalance) return null
|
if (!fiatBalance) return null
|
||||||
|
|
||||||
const notifications = configManager.getNotifications(cryptoCode, null, settings.config)
|
const notifications = configManager.getNotifications(
|
||||||
|
cryptoCode,
|
||||||
|
null,
|
||||||
|
settings.config
|
||||||
|
)
|
||||||
const lowAlertThreshold = notifications.cryptoLowBalance
|
const lowAlertThreshold = notifications.cryptoLowBalance
|
||||||
const highAlertThreshold = notifications.cryptoHighBalance
|
const highAlertThreshold = notifications.cryptoHighBalance
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
cryptoCode,
|
cryptoCode,
|
||||||
fiatBalance,
|
fiatBalance,
|
||||||
fiatCode,
|
fiatCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.isFinite(lowAlertThreshold) && BN(fiatBalance.balance).lt(lowAlertThreshold))
|
if (
|
||||||
|
_.isFinite(lowAlertThreshold) &&
|
||||||
|
BN(fiatBalance.balance).lt(lowAlertThreshold)
|
||||||
|
)
|
||||||
return _.set('code')('LOW_CRYPTO_BALANCE')(req)
|
return _.set('code')('LOW_CRYPTO_BALANCE')(req)
|
||||||
|
|
||||||
if (_.isFinite(highAlertThreshold) && BN(fiatBalance.balance).gt(highAlertThreshold))
|
if (
|
||||||
|
_.isFinite(highAlertThreshold) &&
|
||||||
|
BN(fiatBalance.balance).gt(highAlertThreshold)
|
||||||
|
)
|
||||||
return _.set('code')('HIGH_CRYPTO_BALANCE')(req)
|
return _.set('code')('HIGH_CRYPTO_BALANCE')(req)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
@ -725,24 +728,23 @@ function plugins (settings, deviceId) {
|
||||||
const localeConfig = configManager.getGlobalLocale(settings.config)
|
const localeConfig = configManager.getGlobalLocale(settings.config)
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
const fiatCode = localeConfig.fiatCurrency
|
||||||
|
|
||||||
return machineLoader.getMachines()
|
return machineLoader.getMachines().then(devices => {
|
||||||
.then(devices => {
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
checkCryptoBalances(fiatCode, devices),
|
checkCryptoBalances(fiatCode, devices),
|
||||||
checkDevicesCashBalances(fiatCode, devices)
|
checkDevicesCashBalances(fiatCode, devices)
|
||||||
])
|
]).then(_.flow(_.flattenDeep, _.compact))
|
||||||
.then(_.flow(_.flattenDeep, _.compact))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomCode () {
|
function randomCode () {
|
||||||
return BN(crypto.randomBytes(3).toString('hex'), 16).shift(-6).toFixed(6).slice(-6)
|
return BN(crypto.randomBytes(3).toString('hex'), 16)
|
||||||
|
.shift(-6)
|
||||||
|
.toFixed(6)
|
||||||
|
.slice(-6)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPhoneCode (phone) {
|
function getPhoneCode (phone) {
|
||||||
const code = argv.mockSms
|
const code = argv.mockSms ? '123' : randomCode()
|
||||||
? '123'
|
|
||||||
: randomCode()
|
|
||||||
|
|
||||||
const rec = {
|
const rec = {
|
||||||
sms: {
|
sms: {
|
||||||
|
|
@ -751,14 +753,14 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sms.sendMessage(settings, rec)
|
return sms.sendMessage(settings, rec).then(() => code)
|
||||||
.then(() => code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sweepHdRow (row) {
|
function sweepHdRow (row) {
|
||||||
const cryptoCode = row.crypto_code
|
const cryptoCode = row.crypto_code
|
||||||
|
|
||||||
return wallet.sweep(settings, cryptoCode, row.hd_index)
|
return wallet
|
||||||
|
.sweep(settings, cryptoCode, row.hd_index)
|
||||||
.then(txHash => {
|
.then(txHash => {
|
||||||
if (txHash) {
|
if (txHash) {
|
||||||
logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash)
|
logger.debug('[%s] Swept address with tx: %s', cryptoCode, txHash)
|
||||||
|
|
@ -769,14 +771,17 @@ function plugins (settings, deviceId) {
|
||||||
return db.none(sql, row.id)
|
return db.none(sql, row.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => logger.error('[%s] Sweep error: %s', cryptoCode, err.message))
|
.catch(err =>
|
||||||
|
logger.error('[%s] Sweep error: %s', cryptoCode, err.message)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sweepHd () {
|
function sweepHd () {
|
||||||
const sql = `select id, crypto_code, hd_index from cash_out_txs
|
const sql = `select id, crypto_code, hd_index from cash_out_txs
|
||||||
where hd_index is not null and not swept and status in ('confirmed', 'instant')`
|
where hd_index is not null and not swept and status in ('confirmed', 'instant')`
|
||||||
|
|
||||||
return db.any(sql)
|
return db
|
||||||
|
.any(sql)
|
||||||
.then(rows => Promise.all(rows.map(sweepHdRow)))
|
.then(rows => Promise.all(rows.map(sweepHdRow)))
|
||||||
.catch(err => logger.error(err))
|
.catch(err => logger.error(err))
|
||||||
}
|
}
|
||||||
|
|
@ -790,14 +795,15 @@ function plugins (settings, deviceId) {
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
const fiatCode = localeConfig.fiatCurrency
|
||||||
|
|
||||||
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
|
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
|
||||||
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c))
|
const tickerPromises = cryptoCodes.map(c =>
|
||||||
|
ticker.getRates(settings, fiatCode, c)
|
||||||
|
)
|
||||||
|
|
||||||
return Promise.all(tickerPromises)
|
return Promise.all(tickerPromises)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRates () {
|
function getRates () {
|
||||||
return getRawRates()
|
return getRawRates().then(buildRates)
|
||||||
.then(buildRates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
120
package-lock.json
generated
120
package-lock.json
generated
|
|
@ -57,24 +57,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
"version": "7.12.3",
|
"version": "7.12.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz",
|
||||||
"integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==",
|
"integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/generator": "^7.12.1",
|
"@babel/generator": "^7.12.5",
|
||||||
"@babel/helper-module-transforms": "^7.12.1",
|
"@babel/helper-module-transforms": "^7.12.1",
|
||||||
"@babel/helpers": "^7.12.1",
|
"@babel/helpers": "^7.12.5",
|
||||||
"@babel/parser": "^7.12.3",
|
"@babel/parser": "^7.12.7",
|
||||||
"@babel/template": "^7.10.4",
|
"@babel/template": "^7.12.7",
|
||||||
"@babel/traverse": "^7.12.1",
|
"@babel/traverse": "^7.12.9",
|
||||||
"@babel/types": "^7.12.1",
|
"@babel/types": "^7.12.7",
|
||||||
"convert-source-map": "^1.7.0",
|
"convert-source-map": "^1.7.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.1",
|
"gensync": "^1.0.0-beta.1",
|
||||||
"json5": "^2.1.2",
|
"json5": "^2.1.2",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
|
"resolve": "^1.3.2",
|
||||||
"semver": "^5.4.1",
|
"semver": "^5.4.1",
|
||||||
"source-map": "^0.5.0"
|
"source-map": "^0.5.0"
|
||||||
},
|
},
|
||||||
|
|
@ -155,6 +156,15 @@
|
||||||
"@babel/types": "^7.12.11"
|
"@babel/types": "^7.12.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/helper-get-function-arity": {
|
||||||
|
"version": "7.12.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz",
|
||||||
|
"integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.12.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-validator-identifier": {
|
"@babel/helper-validator-identifier": {
|
||||||
"version": "7.12.11",
|
"version": "7.12.11",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||||
|
|
@ -191,34 +201,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-get-function-arity": {
|
|
||||||
"version": "7.12.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz",
|
|
||||||
"integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@babel/types": "^7.12.10"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/helper-validator-identifier": {
|
|
||||||
"version": "7.12.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
|
||||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@babel/types": {
|
|
||||||
"version": "7.12.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz",
|
|
||||||
"integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@babel/helper-validator-identifier": "^7.12.11",
|
|
||||||
"lodash": "^4.17.19",
|
|
||||||
"to-fast-properties": "^2.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@babel/helper-module-imports": {
|
"@babel/helper-module-imports": {
|
||||||
"version": "7.12.5",
|
"version": "7.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz",
|
||||||
|
|
@ -413,9 +395,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.12.5",
|
"version": "7.12.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz",
|
||||||
"integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==",
|
"integrity": "sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/plugin-syntax-async-generators": {
|
"@babel/plugin-syntax-async-generators": {
|
||||||
|
|
@ -542,28 +524,28 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
"version": "7.10.4",
|
"version": "7.12.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz",
|
||||||
"integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
|
"integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/parser": "^7.10.4",
|
"@babel/parser": "^7.12.7",
|
||||||
"@babel/types": "^7.10.4"
|
"@babel/types": "^7.12.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
"version": "7.12.5",
|
"version": "7.12.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz",
|
||||||
"integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==",
|
"integrity": "sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/generator": "^7.12.5",
|
"@babel/generator": "^7.12.5",
|
||||||
"@babel/helper-function-name": "^7.10.4",
|
"@babel/helper-function-name": "^7.10.4",
|
||||||
"@babel/helper-split-export-declaration": "^7.11.0",
|
"@babel/helper-split-export-declaration": "^7.11.0",
|
||||||
"@babel/parser": "^7.12.5",
|
"@babel/parser": "^7.12.7",
|
||||||
"@babel/types": "^7.12.5",
|
"@babel/types": "^7.12.7",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0",
|
"globals": "^11.1.0",
|
||||||
"lodash": "^4.17.19"
|
"lodash": "^4.17.19"
|
||||||
|
|
@ -596,12 +578,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.12.6",
|
"version": "7.12.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
|
||||||
"integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==",
|
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.12.11",
|
"@babel/helper-validator-identifier": "^7.10.4",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1343,9 +1325,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/babel__traverse": {
|
"@types/babel__traverse": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.16.tgz",
|
||||||
"integrity": "sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==",
|
"integrity": "sha512-S63Dt4CZOkuTmpLGGWtT/mQdVORJOpx6SZWGVaP56dda/0Nx5nEe82K7/LAm8zYr6SfMq+1N2OreIOrHAx656w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.3.0"
|
"@babel/types": "^7.3.0"
|
||||||
|
|
@ -1627,9 +1609,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "15.0.10",
|
"version": "15.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.11.tgz",
|
||||||
"integrity": "sha512-z8PNtlhrj7eJNLmrAivM7rjBESG6JwC5xP3RVk12i/8HVP7Xnx/sEmERnRImyEuUaJfO942X0qMOYsoupaJbZQ==",
|
"integrity": "sha512-jfcNBxHFYJ4nPIacsi3woz1+kvUO6s1CyeEhtnDHBjHUMNj5UlW2GynmnSgiJJEdNg9yW5C8lfoNRZrHGv5EqA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
|
|
@ -7806,9 +7788,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-core-module": {
|
"is-core-module": {
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
|
||||||
"integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==",
|
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
|
|
@ -8350,9 +8332,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
|
|
@ -9331,9 +9313,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue