"use strict"; const moment = require('moment'); const Modem = require('./modem'); const pullgw = require('komodo-sdk/gateway/pull'); const config = require('komodo-sdk/config'); const logger = require('komodo-sdk/logger'); const matrix = require('komodo-sdk/matrix'); const modemDashboard = require('./modem-dashboard'); if (config && config.debug_modem) { process.env.KOMODO_DEBUG_MODEM=1; } if (!config || !config.partner || !config.partner.pin) { logger.warn('Undefined PIN'); process.exit(1); } matrix.modem = {}; matrix.not_ready = true; matrix.not_ready_ts = null; matrix.not_ready_ts_readable = null; matrix.not_ready_max_age_secs = null; matrix.stock = {}; const db = require('./local-db').getConnection(); const pendingArchive = require('./pending-archive'); const patternMatcher = require('./pattern-rule-matcher'); const smsHandler = require('./sms-handler'); const modem = new Modem(config.partner.modem.dev, {baudRate: 115200}); const resumeHandlers = {}; let last_trx_id = null; modem.on('open', function() { logger.info('Modem opened'); const ussd_command = '*776*' + config.partner.pin + '#'; db.run("INSERT INTO ussd VALUES (?, ?, 'OUT', ?, ?)", moment().format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD'), matrix.modem.imsi, 'AT+CUSD=1,"' + ussd_command + '",15', function(err) { if (err) { logger.warn('Error inserting ussd command (stock check) to local database', {err: err}); } }); modem.sendUSSD(ussd_command, function() {}); }) modem.on('imsi', function(imsi) { logger.verbose('IMSI: ' + imsi); matrix.modem.imsi = imsi; }) function onIncomingSMS(sms) { logger.info('Incoming SMS', {sms: sms}); db.run("INSERT INTO sms VALUES (?, ?, 'IN', ?, ?, ?)", sms.created, moment(sms.created).format('YYYY-MM-DD'), matrix.modem.imsi, sms.sender, sms.msg, function(err) { if (err) { logger.warn('Error inserting sms to local database', {err: err}); } }); if (!smsHandler.isAllowedSender(sms.sender)) { logger.verbose('Ignoring SMS from unknown sender', {sender: sms.sender}); return; } const stocks = smsHandler.getMultiStockBalance(sms.msg); if (stocks && Array.isArray(stocks) && stocks.length) { stocks.forEach(function(stock) { const vals = stock.split('='); updateStock(vals[0], vals[1]); }) } else { const stock = smsHandler.getStockBalance(sms.msg); if (stock.name && stock.balance) { updateStock(stock.name, stock.balance); } } const destination = smsHandler.getDestination(sms.msg); if (!destination) { logger.verbose('Ignoring SMS with unknown trx destination'); return; } const product = smsHandler.getProduct(sms.msg); if (!product) { logger.verbose('Ignoring SMS with unknown trx product'); return; } const trx_date = smsHandler.getTrxDate(sms.msg); if (!trx_date) { logger.verbose('Ignoring SMS with unknown trx date'); return; } logger.verbose('SMS message parsed and extracted', {destination: destination, product: product, trx_date: trx_date}); pendingArchive.get(destination, product, trx_date, function(err, trx_id) { if (!trx_id) { logger.verbose('No pending trx suits with SMS', {destination: destination, product: product, trx_date: trx_date}); return; } deleteResumeHandler(trx_id); pendingArchive.remove(trx_id); report({ trx_id: trx_id, rc: smsHandler.getRc(sms.msg) || '68', sn: smsHandler.getSn(sms.msg), message: 'SMS: ' + sms.msg }) }) } modem.on('incoming sms', onIncomingSMS); modem.on('signal strength', function(signal_strength) { matrix.modem.signal_strength = signal_strength; logger.verbose('Signal strength: ' + signal_strength); }) function onUSSDResponse(data) { logger.verbose('Got USSD response', {response: data}); db.run("INSERT INTO ussd VALUES (?, ?, 'IN', ?, ?)", moment().format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD'), matrix.modem.imsi, data, function(err) { if (err) { logger.warn('Error inserting ussd response to local database', {err: err}); } }); if (!last_trx_id) return; const rc = getRcFromMessage(data, config.ussd_parser.rc) || '68'; if (rc !== '68') { onTrxFinish(last_trx_id); } deleteResumeHandler(last_trx_id);; unsuspendPull(); report({ trx_id: last_trx_id, rc: rc, sn: getSnFromMessage(data), message: data }); const stock_name = getStockProductFromMessage(data); const stock_balance = getStockBalanceFromMesssage(data); updateStock(stock_name, stock_balance); last_trx_id = null; } modem.on('ussd response', onUSSDResponse); logger.info('Opening MODEM'); modem.open(function(err) { if (err) { logger.warn('Error opening modem port', {err: err}); process.exit(1); } logger.info('Modem open successfully, going to ready in 30 secs'); setTimeout( unsuspendPull, 30 * 1000 ) }) function updateStock(stock_name, stock_balance) { if (stock_name && (stock_balance !== undefined || stock_balance !== null)) { logger.verbose('Updating stock', {stock_name: stock_name, stock_balance: stock_balance}); const new_stock_name = config && config.remote_product_alias && config.remote_product_alias[stock_name] ? config.remote_product_alias[stock_name] : stock_name; matrix.stock[new_stock_name] = Number(stock_balance); logger.verbose('Stock balance updated', {stock: matrix.stock}); } } function getRcFromMessage(msg, rules) { if (!rules || !Array.isArray(rules)) { return '68'; } for (let rule of rules) { if (!rule.pattern) return '68'; const re = new RegExp(rule.pattern); if (msg.search(re) >= 0) { return rule.rc ? rule.rc : '68'; } } return '68'; } function getPatternMatchFromMessage(msg, rules) { if (!rules || !Array.isArray(rules)) { return; } for (let rule of rules) { if (!rule.pattern) return; const re = new RegExp(rule.pattern); const matches = msg.match(re); if (matches && matches.length >= 2) { return matches[1]; } } } function getSnFromMessage(msg, rules) { return patternMatcher(msg, config.ussd_parser.sn); } function getStockProductFromMessage(msg, rules) { return patternMatcher(msg, config.ussd_parser.stock.product); } function getStockBalanceFromMesssage(msg, rules) { return patternMatcher(msg, config.ussd_parser.stock.balance); } function suspendPull(trx_id) { logger.verbose('Set modem to not ready so no other task can be entered and registering delayed resume'); matrix.not_ready = true; matrix.not_ready_ts = new Date(); matrix.not_ready_ts_readable = moment(matrix.not_ready_ts).format('YYYY-MM-DD HH:mm:ss'); resumeHandlers['trx' + trx_id] = setTimeout(function() { logger.verbose('Resuming supsended modem', {trx_id: trx_id}); unsuspendPull(); report({ trx_id: trx_id, rc: '68', message: 'USSD timeout' }) }, Number(config.partner.modem.suspend_time_ms) || 20 * 1000 ); } function unsuspendPull() { matrix.not_ready = false; if (matrix.not_ready_ts) { matrix.not_ready_max_age_secs = Math.max( (new Date() - matrix.not_ready_ts) / 1000, matrix.not_ready_max_age_secs ); } logger.verbose('Modem is ready'); } function deleteResumeHandler(trx_id) { if (resumeHandlers['trx' + trx_id]) { logger.verbose('Unregistering delayed resume of trx id ' + trx_id); clearTimeout(resumeHandlers['trx' + trx_id]); delete resumeHandlers['trx' + trx_id]; } } function onTrxFinish(trx_id) { deleteResumeHandler(trx_id); pendingArchive.remove(trx_id); unsuspendPull(); } function buy(task) { if (task.product === task.remote_product) { report({ trx_id: task.trx_id, rc: '40', message: 'INTERNAL: Gagal melakukan transaksi. Kode USSD belum terdefinisi.' }); return; } suspendPull(task.trx_id); last_trx_id = task.trx_id; pendingArchive.put(task, function(err) { if (err) { logger.verbose('Error inserting task to pending archive', {trx_id: task.trx_id, destination: task.destination, product: task.product, err: err}); onTrxFinish(task.trx_id); report({ trx_id: task.trx_id, rc: '40', message: 'INTERNAL: Gagal melakukan transaksi. Mungkin ada transaksi dengan nomor dengan produk yang sama yang masih diproses. Silahkan ulangi beberapa saat lagi.' }); return; } const ussd_command = task.remote_product.split(',')[0].replace("<DESTINATION>", task.destination).replace("<PIN>", config.partner.pin); logger.verbose('Going to execute USSD', {trx_id: task.trx_id, destination: task.destination, product: task.product, ussd: ussd_command}); db.run("INSERT INTO ussd VALUES (?, ?, 'OUT', ?, ?)", moment().format('YYYY-MM-DD HH:mm:ss'), moment().format('YYYY-MM-DD'), matrix.modem.imsi, 'AT+CUSD=1,"' + ussd_command + '",15', function(err) { if (err) { logger.warn('Error inserting ussd command to local database', {err: err}); } }); modem.sendUSSD(ussd_command, function() {}); }) } function report(data) { pullgw.report(data); } exports.buy = buy;