Commit a21e7a161eb3b8eeb6e6885d17887b2f29ce635f
1 parent
f031376361
Exists in
master
modem-chooser for sending message
Showing 4 changed files with 73 additions and 2 deletions Inline Diff
config.sample.json
1 | { | 1 | { |
2 | "name": "SMS", | 2 | "name": "SMS", |
3 | 3 | ||
4 | "# messaging_url": "messaging service url", | 4 | "# messaging_url": "messaging service url", |
5 | "messaging_url": "http://localhost:32979/", | 5 | "messaging_url": "http://localhost:32979/", |
6 | 6 | ||
7 | "# listen_port": "HTTP port untuk mendapat perintah dari CORE", | 7 | "# listen_port": "HTTP port untuk mendapat perintah dari CORE", |
8 | "listen_port": 16480, | 8 | "listen_port": 16480, |
9 | 9 | ||
10 | "apiserver": { | 10 | "apiserver": { |
11 | "# listen_port": "HTTP port untuk mendapat pesan masuk dari modem handler", | 11 | "# listen_port": "HTTP port untuk mendapat pesan masuk dari modem handler", |
12 | "listen_port": 16481, | 12 | "listen_port": 16481, |
13 | "apikey": "PLEASE_CHANGE_ME" | 13 | "apikey": "PLEASE_CHANGE_ME" |
14 | }, | 14 | }, |
15 | 15 | ||
16 | "number_suffix": "@phonenumber", | 16 | "number_suffix": "@phonenumber", |
17 | 17 | ||
18 | "modems": { | 18 | "modems": { |
19 | "SMS0": { | 19 | "SMS0": { |
20 | "url": "http://localhost:2110/sms", | 20 | "url": "http://localhost:2110/sms", |
21 | "apikey": "PLEASE_CHANGE_ME" | 21 | "apikey": "PLEASE_CHANGE_ME" |
22 | } | 22 | } |
23 | }, | 23 | }, |
24 | 24 | ||
25 | "do_not_trim_long_sms": false, | 25 | "do_not_trim_long_sms": false, |
26 | 26 | ||
27 | "default_modem": "SMS0", | 27 | "default_modem": "SMS0", |
28 | 28 | ||
29 | "# handler_chooser_algorithm": "algoritma untuk memilih modem dalam mengirim SMS. Pilihan: LAST-SEEN, FORCED. Default: LAST-SEEN", | 29 | "# handler_chooser_algorithm": "algoritma untuk memilih modem dalam mengirim SMS. Pilihan: LAST-SEEN, FORCED. Default: LAST-SEEN", |
30 | "handler_chooser_algorithm": "LAST-SEEN", | 30 | "handler_chooser_algorithm": "LAST-SEEN", |
31 | 31 | ||
32 | "# sending handler": "list modem yang dipakai untuk mengirim jika handler_chooser_algorithm === 'FORCED'", | 32 | "# sending handler": "list modem yang dipakai untuk mengirim jika handler_chooser_algorithm === 'FORCED'", |
33 | "sending_handler": [ | 33 | "sending_handler": [ |
34 | "SMS0", | 34 | "SMS0", |
35 | "SMS1" | 35 | "SMS1" |
36 | ], | 36 | ], |
37 | 37 | ||
38 | "imsi_senders": { | ||
39 | "prefix_names": { | ||
40 | "TELKOMSEL": [ | ||
41 | "510890944235402" | ||
42 | ], | ||
43 | "XL": [ | ||
44 | "510890944262917" | ||
45 | ] | ||
46 | }, | ||
47 | "default": [ | ||
48 | "510890944235513" | ||
49 | ] | ||
50 | }, | ||
51 | |||
38 | "redis": { | 52 | "redis": { |
39 | "host": "127.0.0.1" | 53 | "host": "127.0.0.1" |
40 | } | 54 | } |
41 | } | 55 | } |
index.js
1 | "use strict"; | 1 | "use strict"; |
2 | 2 | ||
3 | process.chdir(__dirname); | 3 | process.chdir(__dirname); |
4 | 4 | ||
5 | const fs = require('fs'); | 5 | const fs = require('fs'); |
6 | fs.writeFileSync('pid.txt', process.pid); | 6 | fs.writeFileSync('pid.txt', process.pid); |
7 | 7 | ||
8 | const config = require('komodo-sdk/config'); | 8 | const config = require('komodo-sdk/config'); |
9 | 9 | ||
10 | global.KOMODO_LOG_LABEL = `KOMODO-CENTER@${(config && typeof config.name === 'string') ? config.name.toUpperCase() : 'SMS'}`; | 10 | global.KOMODO_LOG_LABEL = `KOMODO-CENTER@${(config && typeof config.name === 'string') ? config.name.toUpperCase() : 'SMS'}`; |
11 | process.title = global.KOMODO_LOG_LABEL; | 11 | process.title = global.KOMODO_LOG_LABEL; |
12 | 12 | ||
13 | const logger = require('komodo-sdk/logger'); | ||
14 | global.KOMODO_LOGGER = logger; | ||
15 | |||
13 | require('./lib/prefixes'); | 16 | require('./lib/prefixes'); |
14 | require('./lib/transport'); | 17 | require('./lib/transport'); |
15 | require('./lib/apiserver'); | 18 | require('./lib/apiserver'); |
16 | 19 |
lib/modem-chooser.js
File was created | 1 | 'use strict'; | |
2 | |||
3 | const prefixes = require('./prefixes'); | ||
4 | const modems = require('./modems2'); | ||
5 | const partnerLastSeen = require('./partner-last-seen'); | ||
6 | |||
7 | function filterOutCandidates(candidates) { | ||
8 | if (!Array.isArray(candidates)) { | ||
9 | return []; | ||
10 | } | ||
11 | |||
12 | return candidates.filter((item) => { | ||
13 | const modem = modems.get('by_imsi', item); | ||
14 | |||
15 | if (!modem) return false; | ||
16 | return true; | ||
17 | }); | ||
18 | } | ||
19 | |||
20 | exports.chooser = async function chooser(destination, config) { | ||
21 | const logger = global.KOMODO_LOGGER; | ||
22 | |||
23 | const prefixName = await prefixes.lookup(destination); | ||
24 | if (logger) logger.verbose('Choosing suitable senders', { destination, prefixName }); | ||
25 | |||
26 | let imsiSenders = []; | ||
27 | if (config.imsi_senders && config.imsi_senders.prefix_names && config.imsi_senders.prefix_names[prefixName]) { | ||
28 | imsiSenders = filterOutCandidates(config.imsi_senders.prefix_names[prefixName]); | ||
29 | if (logger) logger.verbose('Suitable senders by prefix name', { destination, prefixName, imsiSenders }); | ||
30 | } | ||
31 | |||
32 | if (!imsiSenders.length && config.imsi_senders.default && Array.isArray(config.imsi_senders.default) && config.imsi_senders.default.length) { | ||
33 | imsiSenders = filterOutCandidates(config.imsi_senders.default); | ||
34 | if (logger) logger.verbose('Suitable default senders', { destination, prefixName, imsiSenders }); | ||
35 | } | ||
36 | |||
37 | if (!imsiSenders.length) { | ||
38 | imsiSenders = filterOutCandidates([ await partnerLastSeen.get(destination) ]); | ||
39 | if (logger) logger.verbose('Suitable senders by last seen', { destination, prefixName, imsiSenders }); | ||
40 | } | ||
41 | |||
42 | if (!imsiSenders.length) { | ||
43 | if (logger) logger.verbose('No suitable sender found', { destination, prefixName }); | ||
44 | return; | ||
45 | } | ||
46 | |||
47 | const count = imsiSenders.length; | ||
48 | const idx = Math.round(Math.random() * (count - 1)); | ||
49 | const imsiChoosed = imsiSenders[idx]; | ||
50 | if (logger) logger.verbose(`Choose modem with IMSI ${imsiChoosed}`, { destination, prefixName, imsiSenders }); | ||
51 | return imsiChoosed; | ||
52 | } |
lib/transport.js
1 | "use strict"; | 1 | "use strict"; |
2 | 2 | ||
3 | const MAX_SMS_LENGTH = 160; | 3 | const MAX_SMS_LENGTH = 160; |
4 | 4 | ||
5 | const url = require('url'); | 5 | const url = require('url'); |
6 | const request = require('request'); | 6 | const request = require('request'); |
7 | const uuidv4 = require('uuid/v4'); | 7 | const uuidv4 = require('uuid/v4'); |
8 | const moment = require('moment'); | 8 | const moment = require('moment'); |
9 | 9 | ||
10 | const config = require('komodo-sdk/config'); | 10 | const config = require('komodo-sdk/config'); |
11 | const logger = require('komodo-sdk/logger'); | 11 | const logger = require('komodo-sdk/logger'); |
12 | 12 | ||
13 | const messagingService = require('komodo-center-messaging-client-lib'); | 13 | const messagingService = require('komodo-center-messaging-client-lib'); |
14 | 14 | ||
15 | const common = require('./common'); | 15 | const common = require('./common'); |
16 | const modems = require('./modems2'); | 16 | const modems = require('./modems2'); |
17 | const partnerLastSeen = require('./partner-last-seen'); | 17 | const modemChooser = require('./modem-chooser'); |
18 | // const partnerLastSeen = require('./partner-last-seen'); | ||
18 | const history = require('./history'); | 19 | const history = require('./history'); |
19 | const prefixes = require('./prefixes'); | 20 | const prefixes = require('./prefixes'); |
20 | 21 | ||
21 | function _send(destinationNumber, msg, handlerIMSI) { | 22 | function _send(destinationNumber, msg, handlerIMSI) { |
22 | 23 | ||
23 | if (msg.length > 160) { | 24 | if (msg.length > 160) { |
24 | logger.info('Splitting message'); | 25 | logger.info('Splitting message'); |
25 | 26 | ||
26 | const newMsg = msg.slice(0, MAX_SMS_LENGTH); | 27 | const newMsg = msg.slice(0, MAX_SMS_LENGTH); |
27 | const remainingMsg = msg.slice(MAX_SMS_LENGTH); | 28 | const remainingMsg = msg.slice(MAX_SMS_LENGTH); |
28 | 29 | ||
29 | _send(destinationNumber, newMsg, handlerIMSI); | 30 | _send(destinationNumber, newMsg, handlerIMSI); |
30 | setTimeout(() => { | 31 | setTimeout(() => { |
31 | _send(destinationNumber, remainingMsg, handlerIMSI); | 32 | _send(destinationNumber, remainingMsg, handlerIMSI); |
32 | }, 1000); | 33 | }, 1000); |
33 | 34 | ||
34 | return; | 35 | return; |
35 | } | 36 | } |
36 | 37 | ||
37 | const modem = modems.get('imsi', handlerIMSI); | 38 | const modem = modems.get('imsi', handlerIMSI); |
38 | if (!modem) { | 39 | if (!modem) { |
39 | logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, modem_imsi: handlerIMSI }); | 40 | logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, modem_imsi: handlerIMSI }); |
40 | return; | 41 | return; |
41 | } | 42 | } |
42 | 43 | ||
43 | if (!modem.reportIp || !modem.reportPort || !modem.reportApikey) { | 44 | if (!modem.reportIp || !modem.reportPort || !modem.reportApikey) { |
44 | logger.warn('Invalid modem configuration', { modem }); | 45 | logger.warn('Invalid modem configuration', { modem }); |
45 | return; | 46 | return; |
46 | } | 47 | } |
47 | 48 | ||
48 | const reqId = uuidv4(); | 49 | const reqId = uuidv4(); |
49 | 50 | ||
50 | history.push({ | 51 | history.push({ |
51 | ts: moment().format('YYYY-MM-DD HH:mm:ss'), | 52 | ts: moment().format('YYYY-MM-DD HH:mm:ss'), |
52 | modem: { | 53 | modem: { |
53 | name: modem.name, | 54 | name: modem.name, |
54 | imsi: modem.imsi, | 55 | imsi: modem.imsi, |
55 | msisdn: modem.msisdn, | 56 | msisdn: modem.msisdn, |
56 | }, | 57 | }, |
57 | direction: 'OUTGOING', | 58 | direction: 'OUTGOING', |
58 | partner: destinationNumber, | 59 | partner: destinationNumber, |
59 | message: msg, | 60 | message: msg, |
60 | }); | 61 | }); |
61 | 62 | ||
62 | logger.verbose('TRANSPORT: saving outgoing message'); | 63 | logger.verbose('TRANSPORT: saving outgoing message'); |
63 | messagingService.onIncomingMessage({ | 64 | messagingService.onIncomingMessage({ |
64 | me: modem.name, | 65 | me: modem.name, |
65 | partner: destinationNumber, | 66 | partner: destinationNumber, |
66 | partner_raw: `+${destinationNumber}`.replace(/^\++/, '+'), | 67 | partner_raw: `+${destinationNumber}`.replace(/^\++/, '+'), |
67 | msg: msg, | 68 | msg: msg, |
68 | origin_label: `IMSI_${modem.imsi || 'UNKNOWN'}`, | 69 | origin_label: `IMSI_${modem.imsi || 'UNKNOWN'}`, |
69 | origin_transport: 'SMS', | 70 | origin_transport: 'SMS', |
70 | origin_partner: destinationNumber, | 71 | origin_partner: destinationNumber, |
71 | do_not_forward_to_core: true, | 72 | do_not_forward_to_core: true, |
72 | is_outgoing: true, | 73 | is_outgoing: true, |
73 | }); | 74 | }); |
74 | 75 | ||
75 | const requestOptions = { | 76 | const requestOptions = { |
76 | url: url.format({ | 77 | url: url.format({ |
77 | protocol: 'http', | 78 | protocol: 'http', |
78 | hostname: modem.reportIp, | 79 | hostname: modem.reportIp, |
79 | port: modem.reportPort, | 80 | port: modem.reportPort, |
80 | pathname: modem.reportPathSms || '/sms', | 81 | pathname: modem.reportPathSms || '/sms', |
81 | }), | 82 | }), |
82 | qs: { | 83 | qs: { |
83 | msg: msg, | 84 | msg: msg, |
84 | number: destinationNumber, | 85 | number: destinationNumber, |
85 | reqid: reqId, | 86 | reqid: reqId, |
86 | apikey: modem.reportApikey, | 87 | apikey: modem.reportApikey, |
87 | } | 88 | } |
88 | } | 89 | } |
89 | 90 | ||
90 | logger.info('Sending message to modem handler', { req_id: reqId, destination_number: destinationNumber, msg: msg, msg_length: msg.length, modem_name: modem.name, modem_imsi: modem.imsi }); | 91 | logger.info('Sending message to modem handler', { req_id: reqId, destination_number: destinationNumber, msg: msg, msg_length: msg.length, modem_name: modem.name, modem_imsi: modem.imsi }); |
91 | request(requestOptions, function(err, res, body) { | 92 | request(requestOptions, function(err, res, body) { |
92 | if (err) { | 93 | if (err) { |
93 | logger.warn('Error requesting to modem handler. ' + err.toString(), { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi }); | 94 | logger.warn('Error requesting to modem handler. ' + err.toString(), { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi }); |
94 | 95 | ||
95 | } | 96 | } |
96 | else if (res.statusCode != 200) { | 97 | else if (res.statusCode != 200) { |
97 | logger.warn('Modem handler not responding with HTTP status code 200.', { http_status_code: res.statusCode, req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi }); | 98 | logger.warn('Modem handler not responding with HTTP status code 200.', { http_status_code: res.statusCode, req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi }); |
98 | } | 99 | } |
99 | else { | 100 | else { |
100 | logger.verbose('Message sent to handler', { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi, response_body: body }); | 101 | logger.verbose('Message sent to handler', { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi, response_body: body }); |
101 | } | 102 | } |
102 | }) | 103 | }) |
103 | 104 | ||
104 | } | 105 | } |
105 | 106 | ||
106 | async function send(partner, msg) { | 107 | async function send(partner, msg) { |
107 | if (!partner) return; | 108 | if (!partner) return; |
108 | 109 | ||
109 | if (typeof msg !== 'string') { | 110 | if (typeof msg !== 'string') { |
110 | logger.warn('Message to send is not a string, ignoring message'); | 111 | logger.warn('Message to send is not a string, ignoring message'); |
111 | return; | 112 | return; |
112 | } | 113 | } |
113 | 114 | ||
114 | msg = msg.trim(); | 115 | msg = msg.trim(); |
115 | if (!msg) return; | 116 | if (!msg) return; |
116 | 117 | ||
117 | const destinationNumber = common.removeSuffixFromNumber(partner, config.number_suffix); | 118 | const destinationNumber = common.removeSuffixFromNumber(partner, config.number_suffix); |
118 | const prefixName = await prefixes.lookup(destinationNumber); | 119 | const prefixName = await prefixes.lookup(destinationNumber); |
119 | logger.verbose('Destination number prefix lookup', {partner: destinationNumber, prefix: prefixName}); | 120 | logger.verbose('Destination number prefix lookup', {partner: destinationNumber, prefix: prefixName}); |
120 | 121 | ||
121 | // logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin }); | 122 | // logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin }); |
122 | const handlerIMSI = await partnerLastSeen.get(destinationNumber) ; | 123 | // const handlerIMSI = await partnerLastSeen.get(destinationNumber) ; |
124 | const handlerIMSI = await modemChooser.chooser(destinationNumber, config); | ||
123 | 125 | ||
124 | if (!handlerIMSI) { | 126 | if (!handlerIMSI) { |
125 | logger.warn(`Unknown handler for sending message to partner`, { partner, destinationNumber }); | 127 | logger.warn(`Unknown handler for sending message to partner`, { partner, destinationNumber }); |
126 | return; | 128 | return; |
127 | } | 129 | } |
128 | 130 | ||
129 | _send(destinationNumber, msg, handlerIMSI); | 131 | _send(destinationNumber, msg, handlerIMSI); |
130 | } | 132 | } |
131 | 133 | ||
132 | exports.send = send; | 134 | exports.send = send; |