africastalking
Advanced tools
Comparing version 0.7.0-beta.1 to 0.7.0
@@ -1,3 +0,3 @@ | ||
'use strict'; | ||
'use strict' | ||
module.exports = require('./lib'); | ||
module.exports = require('./lib') |
@@ -0,588 +1,564 @@ | ||
const validate = require('validate.js') | ||
const _ = require('lodash') | ||
const { phoneValidator } = require('./utils') | ||
const validate = require('validate.js'); | ||
const _ = require('lodash'); | ||
const { phoneValidator } = require('./utils'); | ||
class ActionBuilder { | ||
constructor () { | ||
this.finalized = false; | ||
this.xml = '<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response>'; | ||
constructor () { | ||
this.finalized = false | ||
this.xml = '<?xml version="1.0" encoding="UTF-8"?><Response>' | ||
this.buildAction = function (action) { | ||
this.buildAction = function (action) { | ||
const { | ||
tag, | ||
text, | ||
children, | ||
attributes | ||
} = action | ||
const { | ||
tag, | ||
text, | ||
children, | ||
attributes | ||
} = action; | ||
this.xml = this.xml.concat('<' + tag) | ||
this.xml = this.xml.concat('<' + tag) | ||
if (attributes && Object.keys(attributes).length > 0) { | ||
Object.entries(attributes) | ||
.forEach(([key, value]) => { | ||
this.xml = this.xml.concat(` ${key}="${value}"`) | ||
}) | ||
} | ||
if (attributes && Object.keys(attributes).length > 0) { | ||
Object.entries(attributes) | ||
.forEach(([key, value]) => { | ||
this.xml = this.xml.concat(` ${key}="${value}"`); | ||
}); | ||
if (children && Object.keys(children).length > 0) { | ||
this.xml = this.xml.concat('>') | ||
Object.entries(children) | ||
.forEach(([child, options]) => { | ||
switch (child) { | ||
case 'say': | ||
this.say(options.text, options.attributes) | ||
break | ||
case 'play': | ||
this.play(options.url) | ||
break | ||
case 'getDigits': | ||
this.getDigits(options.children, options.attributes) | ||
break | ||
case 'dial': | ||
this.dial(options.phoneNumbers, options.attributes) | ||
break | ||
case 'record': | ||
this.record(options.children, options.attributes) | ||
break | ||
case 'enqueue': | ||
this.enqueue(options.attributes) | ||
break | ||
case 'dequeue': | ||
this.dequeue(options.phoneNumber, options.attributes) | ||
break | ||
case 'redirect': | ||
this.redirect(options.text) | ||
break | ||
case 'conference': | ||
this.conference() | ||
break | ||
case 'reject': | ||
this.redirect() | ||
break | ||
default: | ||
throw new Error('Invalid child') | ||
} | ||
if (children && Object.keys(children).length > 0) { | ||
this.xml = this.xml.concat('>'); | ||
Object.entries(children) | ||
.forEach(([child, options]) => { | ||
switch (child) { | ||
case 'say': | ||
this.say(options.text, options.attributes); | ||
break; | ||
case 'play': | ||
this.play(options.url); | ||
break; | ||
case 'getDigits': | ||
this.getDigits(options.children, options.attributes); | ||
break; | ||
case 'dial': | ||
this.dial(options.phoneNumbers, options.attributes); | ||
break; | ||
case 'record': | ||
this.record(options.children, options.attributes); | ||
break; | ||
case 'enqueue': | ||
this.enqueue(options.attributes); | ||
break; | ||
case 'dequeue': | ||
this.dequeue(options.phoneNumber, options.attributes); | ||
break; | ||
case 'redirect': | ||
this.redirect(options.text); | ||
break; | ||
case 'conference': | ||
this.conference(); | ||
break; | ||
case 'reject': | ||
this.redirect(); | ||
break; | ||
default: | ||
throw new Error('Invalid child'); | ||
} | ||
}); | ||
this.xml = this.xml.concat(`</${tag}>`); | ||
} else { | ||
if (text) { | ||
this.xml = this.xml.concat(`>${text}</${tag}>`); | ||
} else { | ||
this.xml = this.xml.concat('/>'); | ||
} | ||
} | ||
}) | ||
this.xml = this.xml.concat(`</${tag}>`) | ||
} else { | ||
if (text) { | ||
this.xml = this.xml.concat(`>${text}</${tag}>`) | ||
} else { | ||
this.xml = this.xml.concat('/>') | ||
} | ||
} | ||
} | ||
} | ||
build () { | ||
if (this.finalized) throw new Error('This builder has been finalized by a call to build()'); | ||
build () { | ||
if (this.finalized) throw new Error('This builder has been finalized by a call to build()') | ||
this.finalized = true; | ||
this.xml = this.xml.concat('</Response>'); | ||
return this.xml; | ||
} | ||
this.finalized = true | ||
this.xml = this.xml.concat('</Response>') | ||
return this.xml | ||
} | ||
say(text, attributes = {}) { | ||
let params = _.cloneDeep(attributes); | ||
params.text = text; | ||
say (text, attributes = {}) { | ||
const params = _.cloneDeep(attributes) | ||
params.text = text | ||
let _self = this; | ||
let validationError; | ||
let validationError | ||
let _validateParams = function () { | ||
const constraints = { | ||
text: function (value) { | ||
if (value && !validate.isString(value)) { | ||
return { | ||
format: 'text must be a string' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
voice: function (value) { | ||
let choices = ['man', 'woman']; | ||
if (value && !choices.includes(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null; | ||
}, | ||
playBeep: function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null; | ||
} | ||
}; | ||
let error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
validationError = new Error(msg); | ||
const _validateParams = function () { | ||
const constraints = { | ||
text: function (value) { | ||
if (value && !validate.isString(value)) { | ||
return { | ||
format: 'text must be a string' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
voice: function (value) { | ||
const choices = ['man', 'woman'] | ||
if (value && !choices.includes(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
}, | ||
playBeep: function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
} | ||
} | ||
_validateParams(); | ||
if (validationError) { | ||
throw validationError; | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
const action = { | ||
tag: 'Say', | ||
text, | ||
attributes | ||
} | ||
this.buildAction(action); | ||
return this; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
play(url) { | ||
let attributes = { url }; | ||
let _self = this; | ||
let validationError; | ||
_validateParams() | ||
let _validateParams = function () { | ||
if (validationError) { | ||
throw validationError | ||
} | ||
const constraints = { | ||
url: { | ||
presence: true, | ||
url: true | ||
} | ||
}; | ||
const action = { | ||
tag: 'Say', | ||
text, | ||
attributes | ||
} | ||
let error = validate(attributes, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
this.buildAction(action) | ||
return this | ||
} | ||
validationError = new Error(msg); | ||
} | ||
} | ||
play (url) { | ||
const attributes = { url } | ||
_validateParams(); | ||
let validationError | ||
if (validationError) { | ||
throw validationError; | ||
const _validateParams = function () { | ||
const constraints = { | ||
url: { | ||
presence: true, | ||
url: true | ||
} | ||
} | ||
const action = { | ||
tag: 'Play', | ||
attributes | ||
const error = validate(attributes, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
this.buildAction(action); | ||
return this; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
getDigits(children, attributes = {}) { | ||
let params = { | ||
children: _.cloneDeep(children), | ||
attributes: _.cloneDeep(attributes) | ||
} | ||
_validateParams() | ||
let _self = this; | ||
let validationError; | ||
if (validationError) { | ||
throw validationError | ||
} | ||
let _validateParams = function () { | ||
const action = { | ||
tag: 'Play', | ||
attributes | ||
} | ||
const constraints = { | ||
children: function (value) { | ||
const possibleChildren = ['say', 'play']; | ||
if (value && !possibleChildren.includes(Object.keys(value)[0])) { | ||
return { | ||
format: 'digits has invalid child' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
"attributes.numDigits": function (value) { | ||
if (!validate.isInteger(value)) { | ||
return { | ||
format: 'must be an integer' | ||
} | ||
} | ||
}, | ||
"attributes.timeout": function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
}, | ||
"attributes.callbackUrl": { | ||
url: true | ||
}, | ||
}; | ||
this.buildAction(action) | ||
return this | ||
} | ||
let error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
getDigits (children, attributes = {}) { | ||
const params = { | ||
children: _.cloneDeep(children), | ||
attributes: _.cloneDeep(attributes) | ||
} | ||
validationError = new Error(msg); | ||
let validationError | ||
const _validateParams = function () { | ||
const constraints = { | ||
children: function (value) { | ||
const possibleChildren = ['say', 'play'] | ||
if (value && !possibleChildren.includes(Object.keys(value)[0])) { | ||
return { | ||
format: 'digits has invalid child' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
'attributes.numDigits': function (value) { | ||
if (!validate.isInteger(value)) { | ||
return { | ||
format: 'must be an integer' | ||
} | ||
} | ||
}, | ||
'attributes.timeout': function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
}, | ||
'attributes.callbackUrl': { | ||
url: true | ||
} | ||
} | ||
_validateParams(); | ||
if (validationError) { | ||
throw validationError; | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
const action = { | ||
tag: 'GetDigits', | ||
children, | ||
attributes | ||
} | ||
this.buildAction(action); | ||
return this; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
dial(phoneNumbers, attributes = {}) { | ||
let params = _.cloneDeep(attributes); | ||
params.phoneNumbers = phoneNumbers; | ||
_validateParams() | ||
let _self = this; | ||
let validationError; | ||
if (validationError) { | ||
throw validationError | ||
} | ||
let _validateParams = function () { | ||
const action = { | ||
tag: 'GetDigits', | ||
children, | ||
attributes | ||
} | ||
const constraints = { | ||
phoneNumbers: function (value) { | ||
this.buildAction(action) | ||
return this | ||
} | ||
if (value && !validate.isString(value)) { | ||
return { | ||
format: 'must be a string' | ||
}; | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
record: function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
}, | ||
sequential: function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null; | ||
}, | ||
callerId: function (value) { | ||
dial (phoneNumbers, attributes = {}) { | ||
const params = _.cloneDeep(attributes) | ||
params.phoneNumbers = phoneNumbers | ||
if (!phoneValidator(value).isValid) { | ||
return { | ||
format: 'must not contain invalid phone number' | ||
}; | ||
}; | ||
return null; | ||
}, | ||
ringBackTone: { | ||
url: true | ||
}, | ||
maxDuration: function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
} | ||
}; | ||
let validationError | ||
let error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
validationError = new Error(msg); | ||
const _validateParams = function () { | ||
const constraints = { | ||
phoneNumbers: function (value) { | ||
if (value && !validate.isString(value)) { | ||
return { | ||
format: 'must be a string' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
record: function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
}, | ||
sequential: function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
}, | ||
callerId: function (value) { | ||
if (!phoneValidator(value).isValid) { | ||
return { | ||
format: 'must not contain invalid phone number' | ||
} | ||
}; | ||
return null | ||
}, | ||
ringBackTone: { | ||
url: true | ||
}, | ||
maxDuration: function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
} | ||
} | ||
_validateParams(); | ||
if (validationError) { | ||
throw validationError; | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
const action = { | ||
tag: 'Dial', | ||
attributes: params | ||
} | ||
validationError = new Error(msg) | ||
} | ||
} | ||
this.buildAction(action); | ||
return this; | ||
_validateParams() | ||
if (validationError) { | ||
throw validationError | ||
} | ||
record(children, attributes = {}) { | ||
let params = { | ||
children: _.cloneDeep(children), | ||
attributes: _.cloneDeep(attributes) | ||
} | ||
const action = { | ||
tag: 'Dial', | ||
attributes: params | ||
} | ||
let _self = this; | ||
let validationError; | ||
this.buildAction(action) | ||
return this | ||
} | ||
let _validateParams = function () { | ||
record (children, attributes = {}) { | ||
const params = { | ||
children: _.cloneDeep(children), | ||
attributes: _.cloneDeep(attributes) | ||
} | ||
const constraints = { | ||
children: function (value) { | ||
const possibleChildren = ['say', 'play']; | ||
if (value && !possibleChildren.includes(Object.keys(value)[0])) { | ||
return { | ||
format: 'digits has invalid child' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
"attributes.maxLength": function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
}, | ||
"attributes.timeout": function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
}, | ||
"attributes.trimSilence": function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null; | ||
}, | ||
"attributes.playBeep": function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null; | ||
}, | ||
"attributes.callbackUrl": { | ||
url: true | ||
}, | ||
}; | ||
let validationError | ||
let error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
validationError = new Error(msg); | ||
const _validateParams = function () { | ||
const constraints = { | ||
children: function (value) { | ||
const possibleChildren = ['say', 'play'] | ||
if (value && !possibleChildren.includes(Object.keys(value)[0])) { | ||
return { | ||
format: 'digits has invalid child' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
'attributes.maxLength': function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
}, | ||
'attributes.timeout': function (value) { | ||
if (!validate.isNumber(value)) { | ||
return { | ||
format: 'must be time in seconds' | ||
} | ||
} | ||
}, | ||
'attributes.trimSilence': function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
}, | ||
'attributes.playBeep': function (value) { | ||
if (!validate.isBoolean(value)) { | ||
return { | ||
format: 'invalid option' | ||
} | ||
} | ||
return null | ||
}, | ||
'attributes.callbackUrl': { | ||
url: true | ||
} | ||
} | ||
_validateParams(); | ||
if (validationError) { | ||
throw validationError; | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
const action = { | ||
tag: 'Record', | ||
children, | ||
attributes | ||
} | ||
this.buildAction(action); | ||
return this; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
enqueue(attributes) { | ||
let params = _.cloneDeep(attributes); | ||
let _self = this; | ||
let validationError; | ||
_validateParams() | ||
let _validateParams = function () { | ||
if (validationError) { | ||
throw validationError | ||
} | ||
const constraints = { | ||
holdMusic: { | ||
url: true | ||
} | ||
}; | ||
const action = { | ||
tag: 'Record', | ||
children, | ||
attributes | ||
} | ||
let error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
this.buildAction(action) | ||
return this | ||
} | ||
validationError = new Error(msg); | ||
} | ||
} | ||
enqueue (attributes) { | ||
const params = _.cloneDeep(attributes) | ||
_validateParams(); | ||
let validationError | ||
if (validationError) { | ||
throw validationError; | ||
const _validateParams = function () { | ||
const constraints = { | ||
holdMusic: { | ||
url: true | ||
} | ||
} | ||
const action = { | ||
tag: 'Enqueue', | ||
attributes | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
this.buildAction(action); | ||
return this; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
dequeue(phoneNumber, attributes = {}) { | ||
let params = _.cloneDeep(attributes); | ||
params.phoneNumber = phoneNumber; | ||
_validateParams() | ||
let _self = this; | ||
let validationError; | ||
if (validationError) { | ||
throw validationError | ||
} | ||
let _validateParams = function () { | ||
const action = { | ||
tag: 'Enqueue', | ||
attributes | ||
} | ||
const constraints = { | ||
phoneNumber: function (value) { | ||
this.buildAction(action) | ||
return this | ||
} | ||
if (value && !phoneValidator(value).isValid) { | ||
return { | ||
format: 'must not contain invalid phone number' | ||
}; | ||
} | ||
return { | ||
presence: true | ||
} | ||
} | ||
}; | ||
dequeue (phoneNumber, attributes = {}) { | ||
const params = _.cloneDeep(attributes) | ||
params.phoneNumber = phoneNumber | ||
let error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
let validationError | ||
validationError = new Error(msg); | ||
const _validateParams = function () { | ||
const constraints = { | ||
phoneNumber: function (value) { | ||
if (value && !phoneValidator(value).isValid) { | ||
return { | ||
format: 'must not contain invalid phone number' | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
} | ||
} | ||
_validateParams(); | ||
if (validationError) { | ||
throw validationError; | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
const action = { | ||
tag: 'Dequeue', | ||
attributes: params | ||
} | ||
this.buildAction(action); | ||
return this; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
redirect(text) { | ||
let _self = this; | ||
let validationError; | ||
_validateParams() | ||
let _validateParams = function () { | ||
if (validationError) { | ||
throw validationError | ||
} | ||
const constraints = { | ||
text: function (value) { | ||
const action = { | ||
tag: 'Dequeue', | ||
attributes: params | ||
} | ||
if (value && !validate.isEmpty(value)) { | ||
return { | ||
url: true | ||
}; | ||
} | ||
return { | ||
presence: true | ||
} | ||
}, | ||
}; | ||
this.buildAction(action) | ||
return this | ||
} | ||
let error = validate({ text }, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
redirect (text) { | ||
let validationError | ||
validationError = new Error(msg); | ||
const _validateParams = function () { | ||
const constraints = { | ||
text: function (value) { | ||
if (value && !validate.isEmpty(value)) { | ||
return { | ||
url: true | ||
} | ||
} | ||
return { | ||
presence: true | ||
} | ||
} | ||
} | ||
_validateParams(); | ||
if (validationError) { | ||
throw validationError; | ||
const error = validate({ text }, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
const action = { | ||
tag: 'Redirect', | ||
text | ||
}; | ||
validationError = new Error(msg) | ||
} | ||
} | ||
this.buildAction(action); | ||
return this; | ||
_validateParams() | ||
if (validationError) { | ||
throw validationError | ||
} | ||
conference() { | ||
let _self = this; | ||
const action = { | ||
tag: 'Redirect', | ||
text | ||
} | ||
const action = { | ||
tag: 'Conference' | ||
}; | ||
this.buildAction(action) | ||
return this | ||
} | ||
this.buildAction(action); | ||
return this; | ||
conference () { | ||
const action = { | ||
tag: 'Conference' | ||
} | ||
reject() { | ||
let _self = this; | ||
this.buildAction(action) | ||
return this | ||
} | ||
const action = { | ||
tag: 'Reject' | ||
}; | ||
reject () { | ||
const action = { | ||
tag: 'Reject' | ||
} | ||
this.buildAction(action); | ||
return this; | ||
} | ||
this.buildAction(action) | ||
return this | ||
} | ||
} | ||
module.exports = ActionBuilder; | ||
module.exports = ActionBuilder |
@@ -1,92 +0,92 @@ | ||
'use strict'; | ||
'use strict' | ||
const Joi = require('@hapi/joi'); | ||
const { phoneValidator } = require('./utils'); | ||
const initializeAxios = require('./customAxios'); | ||
const Joi = require('@hapi/joi') | ||
const { phoneValidator } = require('./utils') | ||
const initializeAxios = require('./customAxios') | ||
class Airtime { | ||
constructor(config) { | ||
this.config = config; | ||
} | ||
constructor (config) { | ||
this.config = config | ||
} | ||
send(options) { | ||
return new Promise((resolve, reject) => { | ||
const { error, value } = this.validateOptions(options); | ||
send (options) { | ||
return new Promise((resolve, reject) => { | ||
const { error, value } = this.validateOptions(options) | ||
if (error) { | ||
const combinedMessages = error.details.map(d => d.message).join(';'); | ||
reject(new Error(combinedMessages)); | ||
return; | ||
} | ||
if (error) { | ||
const combinedMessages = error.details.map(d => d.message).join(';') | ||
reject(new Error(combinedMessages)) | ||
return | ||
} | ||
const rawRecipients = value.recipients; | ||
const recipients = rawRecipients.map(r => ({ | ||
phoneNumber: r.phoneNumber, | ||
amount: `${r.currencyCode} ${r.amount}`, | ||
})); | ||
const customAxios = value.idempotencyKey ? initializeAxios(this.config, value.idempotencyKey) : initializeAxios(this.config); | ||
const rawRecipients = value.recipients | ||
const recipients = rawRecipients.map(r => ({ | ||
phoneNumber: r.phoneNumber, | ||
amount: `${r.currencyCode} ${r.amount}` | ||
})) | ||
const customAxios = value.idempotencyKey ? initializeAxios(this.config, value.idempotencyKey) : initializeAxios(this.config) | ||
const requestBody = { | ||
recipients: JSON.stringify(recipients) | ||
}; | ||
const requestBody = { | ||
recipients: JSON.stringify(recipients) | ||
} | ||
if(options.maxNumRetry && (options.maxNumRetry > 0) ){ | ||
requestBody['maxNumRetry'] = Number(options.maxNumRetry); | ||
}; | ||
customAxios.airtime.sendAirtimeRequest(requestBody) | ||
.then(function (response) { | ||
if (response.status === 201) { | ||
resolve(response.data); | ||
} else { | ||
reject(response.data || response.error); | ||
} | ||
}) | ||
.catch(function (err) { | ||
reject(err); | ||
}); | ||
}); | ||
} | ||
if (options.maxNumRetry && (options.maxNumRetry > 0)) { | ||
requestBody.maxNumRetry = Number(options.maxNumRetry) | ||
}; | ||
findTransactionStatus(transactionId) { | ||
return new Promise((resolve, reject) => { | ||
if(!transactionId){ | ||
throw new Error(`transactionId should be provided`); | ||
} | ||
customAxios.airtime.sendAirtimeRequest(requestBody) | ||
.then(function (response) { | ||
if (response.status === 201) { | ||
resolve(response.data) | ||
} else { | ||
reject(response.data || response.error) | ||
} | ||
}) | ||
.catch(function (err) { | ||
reject(err) | ||
}) | ||
}) | ||
} | ||
const { airtime } = initializeAxios(this.config); | ||
findTransactionStatus (transactionId) { | ||
return new Promise((resolve, reject) => { | ||
if (!transactionId) { | ||
throw new Error('transactionId should be provided') | ||
} | ||
airtime.findTransactionStatus(transactionId) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
if (httpStatus === 200) { | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}).catch(function (error) { | ||
return reject(error); | ||
}); | ||
const { airtime } = initializeAxios(this.config) | ||
airtime.findTransactionStatus(transactionId) | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
if (httpStatus === 200) { | ||
resolve(resp.data) | ||
} else { | ||
reject(resp.data) | ||
}; | ||
}).catch(function (error) { | ||
return reject(error) | ||
}) | ||
} | ||
}) | ||
} | ||
validateOptions(options) { | ||
const schema = Joi.object().keys({ | ||
idempotencyKey: Joi.string().optional(), | ||
recipients: Joi.array().items( | ||
Joi.object({ | ||
phoneNumber:Joi.string().required().custom((value)=>{ | ||
return phoneValidator(value).isValid? value : new Error(`the phone number ${value} is invalid`); | ||
}), | ||
currencyCode: Joi.string().required(), | ||
amount: Joi.number().required(), | ||
}), | ||
).min(1).required(), | ||
maxNumRetry: Joi.number().optional(), | ||
}).required(); | ||
validateOptions (options) { | ||
const schema = Joi.object().keys({ | ||
idempotencyKey: Joi.string().optional(), | ||
recipients: Joi.array().items( | ||
Joi.object({ | ||
phoneNumber: Joi.string().required().custom((value) => { | ||
return phoneValidator(value).isValid ? value : new Error(`the phone number ${value} is invalid`) | ||
}), | ||
currencyCode: Joi.string().required(), | ||
amount: Joi.number().required() | ||
}) | ||
).min(1).required(), | ||
maxNumRetry: Joi.number().optional() | ||
}).required() | ||
return schema.validate(options); | ||
} | ||
return schema.validate(options) | ||
} | ||
} | ||
module.exports = Airtime; | ||
module.exports = Airtime |
@@ -1,36 +0,35 @@ | ||
'use strict'; | ||
'use strict' | ||
const initializeAxios = require('./customAxios'); | ||
const initializeAxios = require('./customAxios') | ||
class Application { | ||
constructor(config) { | ||
this.config = config; | ||
} | ||
constructor (config) { | ||
this.config = config | ||
} | ||
fetchApplicationData() { | ||
return new Promise((resolve, reject) => { | ||
const customAxios = initializeAxios(this.config); | ||
customAxios.application.getApplicationData() | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
resolve(response.data); | ||
} else { | ||
reject(response.data || response.error); | ||
} | ||
}) | ||
.catch(function (err) { | ||
reject(err); | ||
}); | ||
fetchApplicationData () { | ||
return new Promise((resolve, reject) => { | ||
const customAxios = initializeAxios(this.config) | ||
}); | ||
} | ||
customAxios.application.getApplicationData() | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
resolve(response.data) | ||
} else { | ||
reject(response.data || response.error) | ||
} | ||
}) | ||
.catch(function (err) { | ||
reject(err) | ||
}) | ||
}) | ||
} | ||
/* backward compatibility */ | ||
fetchAccount() { | ||
return this.fetchApplicationData(); | ||
} | ||
/* end backward compatibility */ | ||
/* backward compatibility */ | ||
fetchAccount () { | ||
return this.fetchApplicationData() | ||
} | ||
/* end backward compatibility */ | ||
} | ||
module.exports = Application; | ||
module.exports = Application |
@@ -1,39 +0,38 @@ | ||
'use strict'; | ||
'use strict' | ||
const BASE_DOMAIN = "africastalking.com"; | ||
const BASE_SANDBOX_DOMAIN = "sandbox." + BASE_DOMAIN; | ||
const BASE_DOMAIN = 'africastalking.com' | ||
const BASE_SANDBOX_DOMAIN = 'sandbox.' + BASE_DOMAIN | ||
const initUrls = function (sandbox) { | ||
const baseDomain = sandbox ? BASE_SANDBOX_DOMAIN : BASE_DOMAIN | ||
const baseUrl = 'https://api.' + baseDomain + '/version1' | ||
const baseDomain = sandbox ? BASE_SANDBOX_DOMAIN : BASE_DOMAIN; | ||
const baseUrl = "https://api." + baseDomain + "/version1"; | ||
exports.BASE_URL = baseUrl | ||
exports.BASE_URL = baseUrl; | ||
exports.CHECKOUT_TOKEN_URL = 'https://api.' + baseDomain + '/checkout/token/create' | ||
exports.CHECKOUT_TOKEN_URL = "https://api." + baseDomain + "/checkout/token/create"; | ||
exports.AUTH_TOKEN_URL = 'https://api.' + baseDomain + '/auth-token/generate' | ||
exports.AUTH_TOKEN_URL = "https://api." + baseDomain + "/auth-token/generate"; | ||
exports.USER_URL = baseUrl + '/user' | ||
exports.USER_URL = baseUrl + "/user"; | ||
exports.SMS_URL = baseUrl + '/messaging' | ||
exports.SMS_URL = baseUrl + "/messaging"; | ||
exports.AIRTIME_URL = baseUrl + '/airtime/send' | ||
exports.AIRTIME_URL = baseUrl + "/airtime/send"; | ||
exports.VOICE_URL = 'https://voice.' + baseDomain | ||
exports.VOICE_URL = "https://voice." + baseDomain; | ||
exports.MOBILE_DATA_URL = 'https://bundles.' + baseDomain | ||
exports.MOBILE_DATA_URL = "https://bundles." + baseDomain; | ||
exports.CONTENT_URL = sandbox | ||
? baseUrl | ||
: 'https://content.' + baseDomain + '/version1' | ||
exports.CONTENT_URL = sandbox | ||
? baseUrl | ||
: 'https://content.' + baseDomain + '/version1'; | ||
exports.INSIGHTS_URL = 'https://insights.' + baseDomain | ||
} | ||
exports.INSIGHTS_URL = "https://insights." + baseDomain; | ||
}; | ||
// no sandbox by default | ||
initUrls(false); | ||
initUrls(false) | ||
exports.enableSandbox = function () { | ||
initUrls(true); | ||
}; | ||
initUrls(true) | ||
} |
@@ -1,22 +0,22 @@ | ||
const qs = require('querystring'); | ||
const qs = require('querystring') | ||
function airtime ({ createAxiosInstance, endpoints, username }) { | ||
return { | ||
sendAirtimeRequest: data => { | ||
return createAxiosInstance().post(endpoints.SEND_AIRTIME, qs.stringify({ | ||
...data, | ||
username, | ||
})); | ||
}, | ||
findTransactionStatus: transactionId => { | ||
return createAxiosInstance('application/json').get(endpoints.FIND_AIRTIME_TRANSACTION, { | ||
params: { | ||
transactionId, | ||
username, | ||
} | ||
}); | ||
return { | ||
sendAirtimeRequest: data => { | ||
return createAxiosInstance().post(endpoints.SEND_AIRTIME, qs.stringify({ | ||
...data, | ||
username | ||
})) | ||
}, | ||
findTransactionStatus: transactionId => { | ||
return createAxiosInstance('application/json').get(endpoints.FIND_AIRTIME_TRANSACTION, { | ||
params: { | ||
transactionId, | ||
username | ||
} | ||
}; | ||
}) | ||
} | ||
} | ||
}; | ||
module.exports = airtime; | ||
module.exports = airtime |
function application ({ createAxiosInstance, endpoints, username }) { | ||
return { | ||
getApplicationData: () => { | ||
return createAxiosInstance().get(endpoints.GET_APPLICATION_DATA, { | ||
params: { username }, | ||
}); | ||
}, | ||
}; | ||
return { | ||
getApplicationData: () => { | ||
return createAxiosInstance().get(endpoints.GET_APPLICATION_DATA, { | ||
params: { username } | ||
}) | ||
} | ||
} | ||
}; | ||
module.exports = application; | ||
module.exports = application |
@@ -1,45 +0,45 @@ | ||
const axios = require('axios'); | ||
const airtime = require('./airtime'); | ||
const application = require('./application'); | ||
const axios = require('axios') | ||
const airtime = require('./airtime') | ||
const application = require('./application') | ||
function initializeAxios (config, idempotencyKey=null) { | ||
const { username, apiKey, format } = config; | ||
function initializeAxios (config, idempotencyKey = null) { | ||
const { username, apiKey, format } = config | ||
const baseURL = username === 'sandbox' | ||
? 'https://api.sandbox.africastalking.com' | ||
: 'https://api.africastalking.com'; | ||
const baseURL = username === 'sandbox' | ||
? 'https://api.sandbox.africastalking.com' | ||
: 'https://api.africastalking.com' | ||
const createAxiosInstance = (contentType = 'application/x-www-form-urlencoded') => { | ||
let headers = { | ||
apiKey, | ||
'Content-Type': contentType, | ||
Accept: format, | ||
}; | ||
if (idempotencyKey) { | ||
headers['Idempotency-Key'] = idempotencyKey; | ||
} | ||
return axios.create({ | ||
baseURL, | ||
headers, | ||
}); | ||
}; | ||
const createAxiosInstance = (contentType = 'application/x-www-form-urlencoded') => { | ||
const headers = { | ||
apiKey, | ||
'Content-Type': contentType, | ||
Accept: format | ||
} | ||
if (idempotencyKey) { | ||
headers['Idempotency-Key'] = idempotencyKey | ||
} | ||
return axios.create({ | ||
baseURL, | ||
headers | ||
}) | ||
} | ||
const endpoints = { | ||
SEND_AIRTIME: '/version1/airtime/send', | ||
FIND_AIRTIME_TRANSACTION: '/query/transaction/find', | ||
GET_APPLICATION_DATA: '/version1/user', | ||
}; | ||
const endpoints = { | ||
SEND_AIRTIME: '/version1/airtime/send', | ||
FIND_AIRTIME_TRANSACTION: '/query/transaction/find', | ||
GET_APPLICATION_DATA: '/version1/user' | ||
} | ||
const opts = { | ||
createAxiosInstance, | ||
endpoints, | ||
username, | ||
}; | ||
const opts = { | ||
createAxiosInstance, | ||
endpoints, | ||
username | ||
} | ||
return { | ||
airtime: airtime(opts), | ||
application: application(opts), | ||
}; | ||
return { | ||
airtime: airtime(opts), | ||
application: application(opts) | ||
} | ||
}; | ||
module.exports = initializeAxios; | ||
module.exports = initializeAxios |
122
lib/index.js
@@ -1,75 +0,73 @@ | ||
'use strict'; | ||
const validate = require('validate.js'); | ||
const _ = require('lodash'); | ||
'use strict' | ||
const validate = require('validate.js') | ||
const _ = require('lodash') | ||
const Common = require('./common'); | ||
const Common = require('./common') | ||
const Application = require('./application'); | ||
const Token = require('./token'); | ||
const SMS = require('./sms'); | ||
const USSD = require('./ussd'); | ||
const Airtime = require('./airtime'); | ||
const Voice = require('./voice'); | ||
const MobileData = require('./mobileData'); | ||
const Insights = require('./insights'); | ||
const Application = require('./application') | ||
const Token = require('./token') | ||
const SMS = require('./sms') | ||
const USSD = require('./ussd') | ||
const Airtime = require('./airtime') | ||
const Voice = require('./voice') | ||
const MobileData = require('./mobileData') | ||
const Insights = require('./insights') | ||
class AfricasTalking { | ||
constructor(options) { | ||
constructor (options) { | ||
this.options = _.cloneDeep(options) | ||
this.options = _.cloneDeep(options); | ||
validate.validators.isString = function (value, options, key, attributes) { | ||
return validate.isEmpty(value) || validate.isString(value) ? null : 'must be a string' | ||
} | ||
validate.validators.isString = function (value, options, key, attributes) { | ||
return validate.isEmpty(value) || validate.isString(value) ? null : "must be a string"; | ||
}; | ||
const constraints = { | ||
format: { | ||
inclusion: ['json', 'xml'] | ||
}, | ||
username: { | ||
presence: true, | ||
isString: true | ||
}, | ||
apiKey: { | ||
presence: true, | ||
isString: true | ||
} | ||
} | ||
const constraints = { | ||
format: { | ||
inclusion: ['json', 'xml'] | ||
}, | ||
username: { | ||
presence: true, | ||
isString: true | ||
}, | ||
apiKey: { | ||
presence: true, | ||
isString: true | ||
} | ||
}; | ||
const error = validate(this.options, constraints) | ||
if (error) { | ||
throw error | ||
} | ||
const error = validate(this.options, constraints); | ||
if (error) { | ||
throw error; | ||
} | ||
switch (this.options.format) { | ||
case 'xml': | ||
this.options.format = 'application/xml' | ||
break | ||
case 'json': // Get json by default | ||
default: | ||
this.options.format = 'application/json' | ||
} | ||
switch (this.options.format) { | ||
case "xml": | ||
this.options.format = "application/xml"; | ||
break; | ||
case "json": // Get json by default | ||
default: | ||
this.options.format = "application/json"; | ||
} | ||
const isSandbox = this.options.username.toLowerCase() === 'sandbox' | ||
if (isSandbox) { | ||
Common.enableSandbox() | ||
} | ||
this.USSD = USSD | ||
this.SMS = new SMS(this.options) | ||
this.TOKEN = new Token(this.options) | ||
this.VOICE = new Voice(this.options) | ||
this.AIRTIME = new Airtime(this.options) | ||
this.INSIGHTS = new Insights(this.options) | ||
this.MOBILE_DATA = new MobileData(this.options) | ||
this.APPLICATION = new Application(this.options) | ||
var isSandbox = this.options.username.toLowerCase() === 'sandbox'; | ||
if (isSandbox) { | ||
Common.enableSandbox(); | ||
} | ||
this.USSD = USSD; | ||
this.SMS = new SMS(this.options); | ||
this.TOKEN = new Token(this.options); | ||
this.VOICE = new Voice(this.options); | ||
this.AIRTIME = new Airtime(this.options); | ||
this.INSIGHTS = new Insights(this.options); | ||
this.MOBILE_DATA = new MobileData(this.options); | ||
this.APPLICATION = new Application(this.options); | ||
/* For backward compatibility */ | ||
this.ACCOUNT = this.APPLICATION; | ||
/* End */ | ||
} | ||
/* For backward compatibility */ | ||
this.ACCOUNT = this.APPLICATION | ||
/* End */ | ||
} | ||
} | ||
module.exports = function (options) { | ||
return new AfricasTalking(options); | ||
}; | ||
return new AfricasTalking(options) | ||
} |
@@ -1,98 +0,102 @@ | ||
'use strict'; | ||
'use strict' | ||
const axios = require('axios'); | ||
const validate = require('validate.js'); | ||
const axios = require('axios') | ||
const validate = require('validate.js') | ||
const Common = require('./common'); | ||
const Common = require('./common') | ||
const { | ||
phoneValidator | ||
} = require('./utils'); | ||
phoneValidator | ||
} = require('./utils') | ||
class Insights { | ||
constructor(options) { | ||
this.options = options; | ||
}; | ||
constructor (options) { | ||
this.options = options | ||
}; | ||
checkSimSwapState(phoneNumbers) { | ||
const _self = this; | ||
checkSimSwapState (phoneNumbers) { | ||
const _self = this | ||
let validationError | ||
const constraints = { | ||
phoneNumbers: function (value) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
}, | ||
}; | ||
} | ||
const constraints = { | ||
phoneNumbers: function (value) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
} | ||
} | ||
if (!validate.isArray(value) && !validate.isString(value)) { | ||
return { | ||
format: 'must be a string or an array strings (phone numbers)', | ||
}; | ||
} | ||
if (!validate.isArray(value) && !validate.isString(value)) { | ||
return { | ||
format: 'must be a string or an array strings (phone numbers)' | ||
} | ||
} | ||
if (validate.isString(value)) { | ||
if (!phoneValidator(value).isValid) { | ||
return { | ||
format: 'must be a valid phone number' | ||
}; | ||
} | ||
} | ||
if (validate.isArray(value)) { | ||
let invalidPhoneNumbers = [] | ||
value.forEach(function (phoneNumber) { | ||
if (!phoneValidator(phoneNumber).isValid) { | ||
invalidPhoneNumbers.push(phoneNumber); | ||
} | ||
}); | ||
if (invalidPhoneNumbers.length > 0) { | ||
return { | ||
format: 'must NOT contain invalid phone number' | ||
} | ||
} | ||
} | ||
return null; | ||
if (validate.isString(value)) { | ||
if (!phoneValidator(value).isValid) { | ||
return { | ||
format: 'must be a valid phone number' | ||
} | ||
}; | ||
} | ||
} | ||
const error = validate({ phoneNumbers }, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + '; '; | ||
if (validate.isArray(value)) { | ||
const invalidPhoneNumbers = [] | ||
value.forEach(function (phoneNumber) { | ||
if (!phoneValidator(phoneNumber).isValid) { | ||
invalidPhoneNumbers.push(phoneNumber) | ||
} | ||
validationError = new Error(msg); | ||
}) | ||
if (invalidPhoneNumbers.length > 0) { | ||
return { | ||
format: 'must NOT contain invalid phone number' | ||
} | ||
} | ||
} | ||
return null | ||
} | ||
} | ||
return new Promise((resolve, reject) => { | ||
const config = { | ||
method: 'post', | ||
url: `${Common.INSIGHTS_URL}/v1/sim-swap`, | ||
headers: { | ||
apiKey: _self.options.apiKey, | ||
Accept: _self.options.format, | ||
'Content-Type': 'application/json' | ||
}, | ||
data: JSON.stringify({ | ||
username: _self.options.username, | ||
phoneNumbers, | ||
}) | ||
}; | ||
axios(config) | ||
.then(function (resp) { | ||
const results = resp.data; | ||
if (!results || results.status !== 'Processed') { | ||
return reject(results || 'Unexpected error'); | ||
}; | ||
return resolve(results) | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
}); | ||
const error = validate({ phoneNumbers }, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
validationError = new Error(msg) | ||
} | ||
return new Promise((resolve, reject) => { | ||
if (validationError) { | ||
return reject(validationError) | ||
} | ||
const config = { | ||
method: 'post', | ||
url: `${Common.INSIGHTS_URL}/v1/sim-swap`, | ||
headers: { | ||
apiKey: _self.options.apiKey, | ||
Accept: _self.options.format, | ||
'Content-Type': 'application/json' | ||
}, | ||
data: JSON.stringify({ | ||
username: _self.options.username, | ||
phoneNumbers | ||
}) | ||
} | ||
axios(config) | ||
.then(function (resp) { | ||
const results = resp.data | ||
if (!results || results.status !== 'Processed') { | ||
return reject(results || 'Unexpected error') | ||
}; | ||
return resolve(results) | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}) | ||
} | ||
}; | ||
module.exports = Insights; | ||
module.exports = Insights |
@@ -1,157 +0,154 @@ | ||
'use strict'; | ||
'use strict' | ||
const Joi = require('@hapi/joi'); | ||
const axios = require('axios'); | ||
const { phoneValidator } = require('./utils'); | ||
const Common = require('./common'); | ||
const Joi = require('@hapi/joi') | ||
const axios = require('axios') | ||
const { phoneValidator } = require('./utils') | ||
const Common = require('./common') | ||
class MobileData { | ||
constructor(options) { | ||
this.options = options; | ||
constructor (options) { | ||
this.options = options | ||
} | ||
validationOptions ({ context, options }) { | ||
const schemas = { | ||
send: Joi.object().keys({ | ||
idempotencyKey: Joi.string().optional(), | ||
productName: Joi.string().required(), | ||
recipients: Joi.array().items( | ||
Joi.object({ | ||
phoneNumber: Joi.string().required().custom((value) => { | ||
return phoneValidator(value).isValid ? value : new Error(`the phone number ${value} is invalid`) | ||
}), | ||
quantity: Joi.number().required(), | ||
unit: Joi.string().valid('MB', 'GB').required(), | ||
validity: Joi.string().valid('Day', 'Week', 'BiWeek', 'Month', 'Quarterly').required(), | ||
metadata: Joi.object().optional() | ||
}) | ||
).min(1).required() | ||
}).required(), | ||
findTransaction: Joi.object().keys({ | ||
transactionId: Joi.string().required() | ||
}).required() | ||
} | ||
validationOptions({context, options}) { | ||
const schemas = { | ||
send: Joi.object().keys({ | ||
idempotencyKey: Joi.string().optional(), | ||
productName: Joi.string().required(), | ||
recipients: Joi.array().items( | ||
Joi.object({ | ||
phoneNumber: Joi.string().required().custom((value) => { | ||
return phoneValidator(value).isValid ? value : new Error(`the phone number ${value} is invalid`); | ||
}), | ||
quantity: Joi.number().required(), | ||
unit: Joi.string().valid('MB', 'GB').required(), | ||
validity: Joi.string().valid('Day', 'Week', 'BiWeek', 'Month', 'Quarterly').required(), | ||
metadata: Joi.object().optional(), | ||
}), | ||
).min(1).required(), | ||
}).required(), | ||
if (!schemas[context]) throw new Error(`schema context '${context}' does not exist`) | ||
findTransaction: Joi.object().keys({ | ||
transactionId: Joi.string().required(), | ||
}).required(), | ||
}; | ||
return schemas[context].validate(options) | ||
}; | ||
if(!schemas[context]) throw new Error(`schema context '${context}' does not exist`); | ||
send (options) { | ||
return new Promise((resolve, reject) => { | ||
const { error, value } = this.validationOptions({ context: 'send', options }) | ||
return schemas[context].validate(options); | ||
}; | ||
if (error) { | ||
const combinedMessages = error.details.map(d => d.message).join(';') | ||
reject(new Error(combinedMessages)) | ||
return | ||
} else { | ||
const headers = { | ||
apikey: this.options.apiKey, | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json' | ||
} | ||
send(options) { | ||
return new Promise((resolve, reject) => { | ||
const { error, value } = this.validationOptions({ context:'send', options}); | ||
if (value.idempotencyKey) { | ||
headers['Idempotency-Key'] = value.idempotencyKey | ||
} | ||
if (error) { | ||
const combinedMessages = error.details.map(d => d.message).join(';'); | ||
reject(new Error(combinedMessages)); | ||
return; | ||
const data = { | ||
username: this.options.username, | ||
productName: value.productName, | ||
recipients: value.recipients | ||
} | ||
axios({ | ||
method: 'post', | ||
url: `${Common.MOBILE_DATA_URL}/mobile/data/request`, | ||
headers, | ||
data: JSON.stringify(data) | ||
}) | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
resolve(resp.data) | ||
} else { | ||
reject(resp.data) | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}; | ||
}) | ||
} | ||
const headers = { | ||
apikey: this.options.apiKey, | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
} | ||
findTransaction (options) { | ||
return new Promise((resolve, reject) => { | ||
const { error, value } = this.validationOptions({ context: 'findTransaction', options }) | ||
if (error) { | ||
const combinedMessages = error.details.map(d => d.message).join(';') | ||
reject(new Error(combinedMessages)) | ||
return | ||
} else { | ||
const username = this.options.username | ||
const transactionId = value.transactionId | ||
const apikey = this.options.apiKey | ||
if (value.idempotencyKey) { | ||
headers['Idempotency-Key'] = value.idempotencyKey; | ||
} | ||
const data = { | ||
username: this.options.username, | ||
productName: value.productName, | ||
recipients: value.recipients | ||
}; | ||
axios({ | ||
method: 'post', | ||
url: `${Common.MOBILE_DATA_URL}/mobile/data/request`, | ||
headers, | ||
data: JSON.stringify(data) | ||
}) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
}; | ||
axios({ | ||
method: 'get', | ||
maxBodyLength: Infinity, | ||
url: `${Common.MOBILE_DATA_URL}/query/transaction/find?username=${username}&transactionId=${transactionId}`, | ||
headers: { | ||
apikey, | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json' | ||
} | ||
}) | ||
} | ||
findTransaction(options){ | ||
return new Promise((resolve, reject) => { | ||
const { error, value } = this.validationOptions({ context:'findTransaction', options}); | ||
if (error) { | ||
const combinedMessages = error.details.map(d => d.message).join(';'); | ||
reject(new Error(combinedMessages)); | ||
return; | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
resolve(resp.data) | ||
} else { | ||
const username = this.options.username; | ||
const transactionId = value.transactionId; | ||
const apikey = this.options.apiKey; | ||
axios({ | ||
method: 'get', | ||
maxBodyLength: Infinity, | ||
url: `${Common.MOBILE_DATA_URL}/query/transaction/find?username=${username}&transactionId=${transactionId}`, | ||
headers: { | ||
apikey, | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
} | ||
}) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
reject(resp.data) | ||
}; | ||
}) | ||
} | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}; | ||
}) | ||
} | ||
fetchWalletBalance(){ | ||
return new Promise((resolve, reject) => { | ||
const username = this.options.username; | ||
const apikey = this.options.apiKey; | ||
axios({ | ||
method: 'get', | ||
maxBodyLength: Infinity, | ||
url: `${Common.MOBILE_DATA_URL}/query/wallet/balance?username=${username}`, | ||
headers: { | ||
apikey, | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
} | ||
}) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
fetchWalletBalance () { | ||
return new Promise((resolve, reject) => { | ||
const username = this.options.username | ||
const apikey = this.options.apiKey | ||
axios({ | ||
method: 'get', | ||
maxBodyLength: Infinity, | ||
url: `${Common.MOBILE_DATA_URL}/query/wallet/balance?username=${username}`, | ||
headers: { | ||
apikey, | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json' | ||
} | ||
}) | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
resolve(resp.data) | ||
} else { | ||
reject(resp.data) | ||
}; | ||
}) | ||
} | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}) | ||
} | ||
} | ||
module.exports = MobileData; | ||
module.exports = MobileData |
747
lib/sms.js
@@ -1,422 +0,415 @@ | ||
'use strict'; | ||
'use strict' | ||
const axios = require('axios'); | ||
const validate = require('validate.js'); | ||
const _ = require('lodash'); | ||
const { phoneValidator } = require('./utils'); | ||
const axios = require('axios') | ||
const validate = require('validate.js') | ||
const _ = require('lodash') | ||
const { phoneValidator } = require('./utils') | ||
const Common = require('./common') | ||
const Common = require('./common'); | ||
class SMS { | ||
constructor(options) { | ||
constructor (options) { | ||
const _self = this | ||
const _self = this; | ||
this.options = options | ||
this.options = options; | ||
this._send = function (params, isBulk, isPremium) { | ||
let validationError | ||
this._send = function (params, isBulk, isPremium) { | ||
let validationError; | ||
// Validate params | ||
const _validateParams = function () { | ||
const constraints = { | ||
to: function (value, attributes, attributeName, options, constraints) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { message: 'is required' } | ||
} | ||
} | ||
// Validate params | ||
const _validateParams = function () { | ||
const constraints = { | ||
to: function (value, attributes, attributeName, options, constraints) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { message: 'is required' }, | ||
}; | ||
} | ||
if (!validate.isArray(value) && !validate.isString(value)) { | ||
return { | ||
format: 'must be a string or an array strings (phone numbers)', | ||
}; | ||
} | ||
if (validate.isString(value)) { | ||
if (!phoneValidator(value).isValid) { | ||
return { | ||
format: 'must be a valid phone number' | ||
}; | ||
} | ||
} | ||
if (validate.isArray(value)) { | ||
let invalidPhoneNumbers = [] | ||
value.forEach(function (phoneNumber) { | ||
if(!phoneValidator(phoneNumber).isValid){ | ||
invalidPhoneNumbers.push(phoneNumber); | ||
} | ||
}); | ||
if (invalidPhoneNumbers.length > 0) { | ||
return { | ||
format: 'must NOT contain invalid phone number' | ||
} | ||
} | ||
} | ||
return null; | ||
}, | ||
from: { | ||
isString: true, | ||
}, | ||
message: { | ||
presence: true, | ||
}, | ||
}; | ||
if (isBulk) { | ||
constraints.enqueue = { | ||
inclusion: [true, false], | ||
}; | ||
if (!validate.isArray(value) && !validate.isString(value)) { | ||
return { | ||
format: 'must be a string or an array strings (phone numbers)' | ||
} | ||
if (isPremium) { | ||
constraints.keyword = { | ||
presence: true, | ||
isString: true, | ||
}; | ||
constraints.linkId = { | ||
presence: false, | ||
isString: true, | ||
}; | ||
constraints.retryDurationInHours = { | ||
numericality: true, | ||
}; | ||
} | ||
} | ||
const error = validate(params, constraints); | ||
if (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + '; '; | ||
if (validate.isString(value)) { | ||
if (!phoneValidator(value).isValid) { | ||
return { | ||
format: 'must be a valid phone number' | ||
} | ||
validationError = new Error(msg); | ||
} | ||
}; | ||
} | ||
_validateParams(); | ||
// Multiple recipients? | ||
if (validate.isArray(params.to)) { | ||
params.to = params.to.join(); | ||
} | ||
return new Promise(function (resolve, reject) { | ||
if (validationError) { | ||
return reject(validationError); | ||
}; | ||
const body = { | ||
username: _self.options.username, | ||
to: params.to, | ||
message: params.message | ||
}; | ||
if (params.from) { | ||
body.from = params.from | ||
if (validate.isArray(value)) { | ||
const invalidPhoneNumbers = [] | ||
value.forEach(function (phoneNumber) { | ||
if (!phoneValidator(phoneNumber).isValid) { | ||
invalidPhoneNumbers.push(phoneNumber) | ||
} | ||
if (isBulk) { | ||
body.bulkSMSMode = 1; | ||
if (params.enqueue) { | ||
body.enqueue = 1; | ||
} | ||
if (params.enqueue === false) { | ||
body.enqueue = 0; | ||
} | ||
}) | ||
if (invalidPhoneNumbers.length > 0) { | ||
return { | ||
format: 'must NOT contain invalid phone number' | ||
} | ||
if (isPremium) { | ||
body.bulkSMSMode = 0; | ||
body.keyword = params.keyword; | ||
body.linkId = params.linkId; | ||
if (params.retryDurationInHours) { | ||
body.retryDurationInHours = params.retryDurationInHours; | ||
} | ||
} | ||
const url = isBulk ? Common.SMS_URL : Common.CONTENT_URL + '/messaging'; | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format, | ||
}; | ||
} | ||
} | ||
return null | ||
}, | ||
from: { | ||
isString: true | ||
}, | ||
message: { | ||
presence: true | ||
} | ||
} | ||
if (isBulk) { | ||
constraints.enqueue = { | ||
inclusion: [true, false] | ||
} | ||
} | ||
if (isPremium) { | ||
constraints.keyword = { | ||
presence: true, | ||
isString: true | ||
} | ||
constraints.linkId = { | ||
presence: false, | ||
isString: true | ||
} | ||
constraints.retryDurationInHours = { | ||
numericality: true | ||
} | ||
} | ||
axios({ | ||
method:'POST', | ||
url, | ||
headers, | ||
data: new URLSearchParams(body) | ||
}) | ||
.then(function (response) { | ||
if (response.status === 201) { | ||
// API returns CREATED on success!? | ||
resolve(response.data); | ||
} else { | ||
reject(response.data); | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}); | ||
const error = validate(params, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
validationError = new Error(msg) | ||
} | ||
} | ||
_validateParams() | ||
// Multiple recipients? | ||
if (validate.isArray(params.to)) { | ||
params.to = params.to.join() | ||
} | ||
return new Promise(function (resolve, reject) { | ||
if (validationError) { | ||
return reject(validationError) | ||
} | ||
const body = { | ||
username: _self.options.username, | ||
to: params.to, | ||
message: params.message | ||
} | ||
if (params.from) { | ||
body.from = params.from | ||
} | ||
if (isBulk) { | ||
body.bulkSMSMode = 1 | ||
if (params.enqueue) { | ||
body.enqueue = 1 | ||
} | ||
if (params.enqueue === false) { | ||
body.enqueue = 0 | ||
} | ||
} | ||
if (isPremium) { | ||
body.bulkSMSMode = 0 | ||
body.keyword = params.keyword | ||
body.linkId = params.linkId | ||
if (params.retryDurationInHours) { | ||
body.retryDurationInHours = params.retryDurationInHours | ||
} | ||
} | ||
const url = isBulk ? Common.SMS_URL : Common.CONTENT_URL + '/messaging' | ||
}); | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
} | ||
axios({ | ||
method: 'POST', | ||
url, | ||
headers, | ||
data: new URLSearchParams(body) | ||
}) | ||
.then(function (response) { | ||
if (response.status === 201) { | ||
// API returns CREATED on success!? | ||
resolve(response.data) | ||
} else { | ||
reject(response.data) | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}) | ||
}) | ||
} | ||
} | ||
send(params){ | ||
const opts = _.cloneDeep(params); | ||
send (params) { | ||
const opts = _.cloneDeep(params) | ||
if (Array.isArray(opts)) { | ||
const results = opts.map((opt) => { | ||
return this._send(opt, true, false); | ||
}); | ||
if (Array.isArray(opts)) { | ||
const results = opts.map((opt) => { | ||
return this._send(opt, true, false) | ||
}) | ||
return Promise.allSettled(results).then((results) => { | ||
return results.map((result) => { | ||
if (result.status === 'fulfilled') { | ||
return result.value; | ||
} else { | ||
return { | ||
SMSMessageData: { | ||
Message: result.reason, | ||
status: 'failed', | ||
}, | ||
}; | ||
} | ||
}); | ||
}); | ||
} else { | ||
return this._send(opts, true, false); | ||
} | ||
return Promise.allSettled(results).then((results) => { | ||
return results.map((result) => { | ||
if (result.status === 'fulfilled') { | ||
return result.value | ||
} else { | ||
return { | ||
SMSMessageData: { | ||
Message: result.reason, | ||
status: 'failed' | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
} else { | ||
return this._send(opts, true, false) | ||
} | ||
} | ||
sendBulk(params) { | ||
return this.send(params); | ||
} | ||
sendBulk (params) { | ||
return this.send(params) | ||
} | ||
sendPremium (params) { | ||
const opts = _.cloneDeep(params) | ||
return this._send(opts, false, true) | ||
} | ||
sendPremium(params) { | ||
const opts = _.cloneDeep(params); | ||
return this._send(opts, false, true); | ||
} | ||
fetchMessages = function (params) { | ||
const _self = this | ||
const opts = _.cloneDeep(params) || {} | ||
opts.lastReceivedId = opts.lastReceivedId || 0 | ||
return new Promise(function (resolve, reject) { | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
} | ||
fetchMessages = function (params) { | ||
const _self = this; | ||
const opts = _.cloneDeep(params) || {}; | ||
opts.lastReceivedId = opts.lastReceivedId || 0; | ||
return new Promise(function (resolve, reject) { | ||
const query = new URLSearchParams({ | ||
username: _self.options.username, | ||
lastReceivedId: opts.lastReceivedId, | ||
keyword: opts.keyword, | ||
shortCode: opts.shortCode | ||
}).toString() | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
}; | ||
const url = `${Common.SMS_URL}?${query}` | ||
const query = new URLSearchParams({ | ||
username: _self.options.username, | ||
lastReceivedId: opts.lastReceivedId, | ||
keyword: opts.keyword, | ||
shortCode: opts.shortCode, | ||
}).toString(); | ||
axios({ | ||
method: 'GET', | ||
url, | ||
headers | ||
}) | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
resolve(response.data) | ||
} else { | ||
reject(response.data) | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}) | ||
}) | ||
} | ||
const url = `${Common.SMS_URL}?${query}`; | ||
createSubscription (params) { | ||
const _self = this | ||
const opts = _.cloneDeep(params) || {} | ||
const constraints = { | ||
shortCode: { | ||
presence: true, | ||
isString: true | ||
}, | ||
keyword: { | ||
presence: true, | ||
isString: true | ||
}, | ||
phoneNumber: { | ||
presence: true, | ||
isString: true | ||
} | ||
} | ||
const validationError = validate(opts, constraints) | ||
const body = { | ||
username: _self.options.username, | ||
shortCode: opts.shortCode, | ||
keyword: opts.keyword, | ||
phoneNumber: opts.phoneNumber | ||
} | ||
return new Promise(function (resolve, reject) { | ||
if (validationError) { | ||
return reject(validationError) | ||
} | ||
axios({ | ||
method:'GET', | ||
url, | ||
headers | ||
}) | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
resolve(response.data); | ||
} else { | ||
reject(response.data); | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}); | ||
}); | ||
}; | ||
const url = `${Common.CONTENT_URL}/subscription/create` | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
} | ||
createSubscription(params) { | ||
const _self = this; | ||
const opts = _.cloneDeep(params) || {}; | ||
const constraints = { | ||
shortCode: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
keyword: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
phoneNumber: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
}; | ||
const validationError = validate(opts, constraints); | ||
const body = { | ||
username: _self.options.username, | ||
shortCode: opts.shortCode, | ||
keyword: opts.keyword, | ||
phoneNumber: opts.phoneNumber, | ||
}; | ||
return new Promise(function (resolve, reject) { | ||
if (validationError) { | ||
return reject(validationError); | ||
axios({ | ||
method: 'POST', | ||
url, | ||
headers, | ||
data: new URLSearchParams(body) | ||
}) | ||
.then(function (response) { | ||
if (response.status === 201) { | ||
// API returns CREATED on success!? | ||
resolve(response.data) | ||
} else { | ||
reject(response.data) | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}) | ||
}) | ||
}; | ||
const url = `${Common.CONTENT_URL}/subscription/create`; | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
}; | ||
fetchSubscription (params) { | ||
const _self = this | ||
const opts = _.cloneDeep(params) || {} | ||
axios({ | ||
method:'POST', | ||
url, | ||
headers, | ||
data: new URLSearchParams(body) | ||
}) | ||
.then(function (response) { | ||
if (response.status === 201) { | ||
// API returns CREATED on success!? | ||
resolve(response.data); | ||
} else { | ||
reject(response.data); | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}); | ||
}); | ||
}; | ||
const constraints = { | ||
shortCode: { | ||
presence: true, | ||
isString: true | ||
}, | ||
keyword: { | ||
presence: true, | ||
isString: true | ||
}, | ||
lastReceivedId: { | ||
numericality: true | ||
} | ||
} | ||
const validationError = validate(opts, constraints) | ||
opts.lastReceivedId = opts.lastReceivedId || 0 | ||
return new Promise(function (resolve, reject) { | ||
// throw validation error inside the promise chain | ||
if (validationError) { | ||
return reject(validationError) | ||
}; | ||
fetchSubscription(params) { | ||
const _self = this; | ||
const opts = _.cloneDeep(params) || {}; | ||
const constraints = { | ||
shortCode: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
keyword: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
lastReceivedId: { | ||
numericality: true, | ||
}, | ||
}; | ||
const validationError = validate(opts, constraints); | ||
opts.lastReceivedId = opts.lastReceivedId || 0; | ||
return new Promise(function (resolve, reject) { | ||
// throw validation error inside the promise chain | ||
if (validationError) { | ||
return reject(validationError); | ||
}; | ||
const headers ={ | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format, | ||
}; | ||
const headers = { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
} | ||
const query = new URLSearchParams({ | ||
username: _self.options.username, | ||
lastReceivedId: opts.lastReceivedId, | ||
keyword: opts.keyword, | ||
shortCode: opts.shortCode, | ||
}).toString(); | ||
const query = new URLSearchParams({ | ||
username: _self.options.username, | ||
lastReceivedId: opts.lastReceivedId, | ||
keyword: opts.keyword, | ||
shortCode: opts.shortCode | ||
}).toString() | ||
const url = `${Common.CONTENT_URL}/subscription?${query}`; | ||
const url = `${Common.CONTENT_URL}/subscription?${query}` | ||
axios({ | ||
method:'GET', | ||
url, | ||
headers | ||
}) | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
resolve(response.data); | ||
} else { | ||
reject(response.data); | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}); | ||
}); | ||
}; | ||
deleteSubscription(params) { | ||
const _self = this; | ||
const options = _.cloneDeep(params); | ||
let validationError; | ||
const _validateParams = function () { | ||
const constraints = { | ||
shortCode: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
keyword: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
phoneNumber: { | ||
presence: true, | ||
isString: true, | ||
}, | ||
}; | ||
const error = validate(options, constraints); | ||
const makeErrorMessage = function (error) { | ||
let msg = ''; | ||
for (let k in error) { | ||
msg += error[k] + '; '; | ||
} | ||
validationError = new Error(msg); | ||
}; | ||
if (error) { | ||
makeErrorMessage(error); | ||
axios({ | ||
method: 'GET', | ||
url, | ||
headers | ||
}) | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
resolve(response.data) | ||
} else { | ||
reject(response.data) | ||
} | ||
}; | ||
_validateParams(); | ||
return new Promise(function (resolve, reject) { | ||
if (validationError) { | ||
return reject(validationError); | ||
} | ||
const { username, apiKey, format } = _self.options; | ||
const { shortCode, keyword, phoneNumber } = options; | ||
const body = { | ||
username, | ||
shortCode, | ||
keyword, | ||
phoneNumber, | ||
}; | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}) | ||
}) | ||
}; | ||
const url = `${Common.CONTENT_URL}/subscription/delete`; | ||
const headers = { | ||
apiKey: apiKey, | ||
Accept: format, | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}; | ||
deleteSubscription (params) { | ||
const _self = this | ||
const options = _.cloneDeep(params) | ||
let validationError | ||
const _validateParams = function () { | ||
const constraints = { | ||
shortCode: { | ||
presence: true, | ||
isString: true | ||
}, | ||
keyword: { | ||
presence: true, | ||
isString: true | ||
}, | ||
phoneNumber: { | ||
presence: true, | ||
isString: true | ||
} | ||
} | ||
const error = validate(options, constraints) | ||
axios({ | ||
method:'POST', | ||
url, | ||
headers, | ||
data: new URLSearchParams(body) | ||
}) | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
// API deleted successfully | ||
resolve(response.data); | ||
} else { | ||
reject(response.data); | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}); | ||
}); | ||
}; | ||
const makeErrorMessage = function (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
validationError = new Error(msg) | ||
} | ||
if (error) { | ||
makeErrorMessage(error) | ||
} | ||
} | ||
_validateParams() | ||
return new Promise(function (resolve, reject) { | ||
if (validationError) { | ||
return reject(validationError) | ||
} | ||
const { username, apiKey, format } = _self.options | ||
const { shortCode, keyword, phoneNumber } = options | ||
const body = { | ||
username, | ||
shortCode, | ||
keyword, | ||
phoneNumber | ||
} | ||
const url = `${Common.CONTENT_URL}/subscription/delete` | ||
const headers = { | ||
apiKey, | ||
Accept: format, | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
} | ||
axios({ | ||
method: 'POST', | ||
url, | ||
headers, | ||
data: new URLSearchParams(body) | ||
}) | ||
.then(function (response) { | ||
if (response.status === 200) { | ||
// API deleted successfully | ||
resolve(response.data) | ||
} else { | ||
reject(response.data) | ||
} | ||
}) | ||
.catch(function (error) { | ||
reject(error) | ||
}) | ||
}) | ||
}; | ||
}; | ||
module.exports = SMS; | ||
module.exports = SMS |
@@ -1,40 +0,40 @@ | ||
'use strict'; | ||
'use strict' | ||
const axios = require('axios'); | ||
const Common = require('./common'); | ||
class Token{ | ||
constructor(options){ | ||
this.options = options; | ||
}; | ||
const axios = require('axios') | ||
const Common = require('./common') | ||
class Token { | ||
constructor (options) { | ||
this.options = options | ||
}; | ||
generateAuthToken() { | ||
const _self = this; | ||
return new Promise((resolve,reject) => { | ||
const config = { | ||
method: 'post', | ||
url: Common.AUTH_TOKEN_URL, | ||
headers: { | ||
apiKey: _self.options.apiKey, | ||
Accept: _self.options.format, | ||
'Content-Type': 'application/json' | ||
}, | ||
data : JSON.stringify({ | ||
username: _self.options.username, | ||
}) | ||
}; | ||
axios(config) | ||
.then(function (resp) { | ||
const results = resp.data; | ||
if (!results.token || results.token === 'None') { | ||
return reject(results.description); | ||
}; | ||
return resolve(results) | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
}); | ||
} | ||
generateAuthToken () { | ||
const _self = this | ||
return new Promise((resolve, reject) => { | ||
const config = { | ||
method: 'post', | ||
url: Common.AUTH_TOKEN_URL, | ||
headers: { | ||
apiKey: _self.options.apiKey, | ||
Accept: _self.options.format, | ||
'Content-Type': 'application/json' | ||
}, | ||
data: JSON.stringify({ | ||
username: _self.options.username | ||
}) | ||
} | ||
axios(config) | ||
.then(function (resp) { | ||
const results = resp.data | ||
if (!results.token || results.token === 'None') { | ||
return reject(results.description) | ||
}; | ||
return resolve(results) | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}) | ||
} | ||
}; | ||
module.exports = Token; | ||
module.exports = Token |
@@ -1,11 +0,11 @@ | ||
'use strict'; | ||
'use strict' | ||
const bodyParser = require('body-parser'); | ||
const validate = require('validate.js'); | ||
const bodyParser = require('body-parser') | ||
const validate = require('validate.js') | ||
// as per specs http://docs.africastalking.com/ussd | ||
const CONTENT_TYPE = "text/plain"; | ||
const SESSION_CONTINUE = "CON "; | ||
const SESSION_END = "END "; | ||
const RESPONSE_CODE = 200; | ||
const CONTENT_TYPE = 'text/plain' | ||
const SESSION_CONTINUE = 'CON ' | ||
const SESSION_END = 'END ' | ||
const RESPONSE_CODE = 200 | ||
@@ -18,49 +18,45 @@ /** | ||
*/ | ||
function ExpressHandler(handler) { | ||
function ExpressHandler (handler) { | ||
return [ // connect/express middleware | ||
return [ // connect/express middleware | ||
bodyParser.urlencoded({ extended: true }), | ||
bodyParser.json(), | ||
bodyParser.urlencoded({extended: true}), | ||
bodyParser.json(), | ||
function (req, res, next) { | ||
const params = { | ||
sessionId: req.body.sessionId, | ||
serviceCode: req.body.serviceCode, | ||
phoneNumber: req.body.phoneNumber, | ||
text: req.body.text | ||
} | ||
function (req, res, next) { | ||
handler(params, (opts) => { | ||
const badOptions = validate(opts, { | ||
response: { | ||
presence: true, | ||
isString: true | ||
}, | ||
endSession: { | ||
presence: true, | ||
inclusion: [true, false] | ||
} | ||
}) | ||
const params = { | ||
sessionId: req.body.sessionId, | ||
serviceCode: req.body.serviceCode, | ||
phoneNumber: req.body.phoneNumber, | ||
text: req.body.text | ||
}; | ||
// express uses next(err) to pass back errors | ||
if (badOptions) { | ||
return next(badOptions) | ||
} | ||
handler(params, (opts) => { | ||
let response = opts.response | ||
const badOptions = validate(opts, { | ||
response: { | ||
presence: true, | ||
isString: true | ||
}, | ||
endSession: { | ||
presence: true, | ||
inclusion: [true, false] | ||
} | ||
}); | ||
// express uses next(err) to pass back errors | ||
if (badOptions) { | ||
return next(badOptions); | ||
} | ||
let response = opts.response; | ||
if (opts.endSession) { | ||
response = SESSION_END + response; | ||
} else { | ||
response = SESSION_CONTINUE + response; | ||
} | ||
res.contentType(CONTENT_TYPE); | ||
res.status(RESPONSE_CODE).send(response); | ||
}); | ||
if (opts.endSession) { | ||
response = SESSION_END + response | ||
} else { | ||
response = SESSION_CONTINUE + response | ||
} | ||
] | ||
res.contentType(CONTENT_TYPE) | ||
res.status(RESPONSE_CODE).send(response) | ||
}) | ||
} | ||
] | ||
} | ||
@@ -70,2 +66,2 @@ | ||
module.exports = ExpressHandler; | ||
module.exports = ExpressHandler |
@@ -1,37 +0,34 @@ | ||
const libphonenumber = require('google-libphonenumber'); | ||
const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance(); | ||
const libphonenumber = require('google-libphonenumber') | ||
const phoneUtil = libphonenumber.PhoneNumberUtil.getInstance() | ||
function phoneValidator(suspiciousPhoneNumber){ | ||
try{ | ||
// Parse the phone number | ||
const parsedNumber = phoneUtil.parseAndKeepRawInput(suspiciousPhoneNumber, null); | ||
function phoneValidator (suspiciousPhoneNumber) { | ||
try { | ||
// Parse the phone number | ||
const parsedNumber = phoneUtil.parseAndKeepRawInput(suspiciousPhoneNumber, null) | ||
// Check if the phone number is valid | ||
if (phoneUtil.isValidNumber(parsedNumber)) { | ||
// Check if the phone number is valid | ||
if (phoneUtil.isValidNumber(parsedNumber)) { | ||
// Get the country code | ||
const countryCode = phoneUtil.getRegionCodeForNumber(parsedNumber) | ||
const phoneNumber = phoneUtil.format(parsedNumber, libphonenumber.PhoneNumberFormat.E164) | ||
// Get the country code | ||
const countryCode = phoneUtil.getRegionCodeForNumber(parsedNumber); | ||
const phoneNumber = phoneUtil.format(parsedNumber, libphonenumber.PhoneNumberFormat.E164); | ||
return { | ||
isValid: true, | ||
countryCode, | ||
phoneNumber, | ||
}; | ||
} else { | ||
return { | ||
isValid: false, | ||
errorMessage: 'Invalid phone number', | ||
}; | ||
} | ||
}catch(error){ | ||
return { | ||
isValid: false, | ||
errorMessage: 'Error parsing phone number', | ||
}; | ||
return { | ||
isValid: true, | ||
countryCode, | ||
phoneNumber | ||
} | ||
} else { | ||
return { | ||
isValid: false, | ||
errorMessage: 'Invalid phone number' | ||
} | ||
} | ||
} catch (error) { | ||
return { | ||
isValid: false, | ||
errorMessage: 'Error parsing phone number' | ||
} | ||
} | ||
} | ||
module.exports = { phoneValidator }; | ||
module.exports = { phoneValidator } |
445
lib/voice.js
@@ -1,260 +0,257 @@ | ||
'use strict'; | ||
const validate = require('validate.js'); | ||
const _ = require('lodash'); | ||
const axios = require('axios'); | ||
const { phoneValidator } = require('./utils'); | ||
'use strict' | ||
const validate = require('validate.js') | ||
const _ = require('lodash') | ||
const axios = require('axios') | ||
const { phoneValidator } = require('./utils') | ||
const Common = require('./common'); | ||
const Common = require('./common') | ||
const Builder = require('./actionbuilder') | ||
class Voice { | ||
constructor(options) { | ||
this.options = options; | ||
this.ActionBuilder = Builder; | ||
} | ||
call({callFrom, callTo, clientRequestId}){ | ||
return new Promise((resolve,reject)=>{ | ||
if(!callTo || !callFrom){ | ||
reject(`Both "callTo" and "callFrom" are required`) | ||
}; | ||
constructor (options) { | ||
this.options = options | ||
this.ActionBuilder = Builder | ||
} | ||
if(typeof callTo === 'string'){ | ||
callTo = callTo.split(','); | ||
}; | ||
call ({ callFrom, callTo, clientRequestId }) { | ||
return new Promise((resolve, reject) => { | ||
if (!callTo || !callFrom) { | ||
reject(new Error('Both "callTo" and "callFrom" are required')) | ||
}; | ||
if(!Array.isArray(callTo)){ | ||
reject('"callTo" can only be an array of phoneNumbers, or a string of comma-separated phoneNumbers'); | ||
}; | ||
if (typeof callTo === 'string') { | ||
callTo = callTo.split(',') | ||
}; | ||
const constraints = { | ||
clientRequestId: { | ||
isString: true, | ||
}, | ||
callFrom:function (value) { | ||
const suspect = phoneValidator(value); | ||
if(suspect.isValid){ | ||
callFrom = suspect.phoneNumber; | ||
return null; | ||
}else{ | ||
return { format: "callFrom must be a valid phonenumber" } | ||
}; | ||
}, | ||
callTo: function(value){ | ||
const invalidPhoneNumbers = []; | ||
const validPhoneNumbers = []; | ||
if (!Array.isArray(callTo)) { | ||
reject(new Error('"callTo" can only be an array of phoneNumbers, or a string of comma-separated phoneNumbers')) | ||
}; | ||
value.forEach(function (phoneNumber) { | ||
const suspect = phoneValidator(phoneNumber); | ||
if(suspect.isValid){ | ||
validPhoneNumbers.push(suspect.phoneNumber) | ||
}else{ | ||
invalidPhoneNumbers.push(phoneNumber); | ||
}; | ||
}); | ||
const constraints = { | ||
clientRequestId: { | ||
isString: true | ||
}, | ||
callFrom: function (value) { | ||
const suspect = phoneValidator(value) | ||
if (suspect.isValid) { | ||
callFrom = suspect.phoneNumber | ||
return null | ||
} else { | ||
return { format: 'callFrom must be a valid phonenumber' } | ||
}; | ||
}, | ||
callTo: function (value) { | ||
const invalidPhoneNumbers = [] | ||
const validPhoneNumbers = [] | ||
if(invalidPhoneNumbers.length){ | ||
return { | ||
format: `callTo contains the following invalid phoneNumber(s): ${invalidPhoneNumbers}` | ||
} | ||
}else{ | ||
callTo = validPhoneNumbers; | ||
return null; | ||
} | ||
} | ||
value.forEach(function (phoneNumber) { | ||
const suspect = phoneValidator(phoneNumber) | ||
if (suspect.isValid) { | ||
validPhoneNumbers.push(suspect.phoneNumber) | ||
} else { | ||
invalidPhoneNumbers.push(phoneNumber) | ||
}; | ||
}) | ||
const error = validate({callFrom, callTo, clientRequestId}, constraints); | ||
if (error) { | ||
let msg = ""; | ||
for (var k in error) { | ||
msg += error[k] + "; "; | ||
}; | ||
reject(msg); | ||
}else{ | ||
if (invalidPhoneNumbers.length) { | ||
return { | ||
format: `callTo contains the following invalid phoneNumber(s): ${invalidPhoneNumbers}` | ||
} | ||
} else { | ||
callTo = validPhoneNumbers | ||
return null | ||
} | ||
} | ||
} | ||
const config = { | ||
method: 'post', | ||
url: `${Common.VOICE_URL}/call`, | ||
headers: { | ||
apikey: this.options.apiKey, | ||
Accept: this.options.format, | ||
"Content-Type":"application/x-www-form-urlencoded" | ||
}, | ||
data : new URLSearchParams({ | ||
username: this.options.username, | ||
to: callTo.join(','), | ||
from: callFrom, | ||
clientRequestId: clientRequestId | ||
}) | ||
}; | ||
axios(config) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
const error = validate({ callFrom, callTo, clientRequestId }, constraints) | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
// API returns CREATED on success | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
}; | ||
reject(msg) | ||
} else { | ||
const config = { | ||
method: 'post', | ||
url: `${Common.VOICE_URL}/call`, | ||
headers: { | ||
apikey: this.options.apiKey, | ||
Accept: this.options.format, | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
}, | ||
data: new URLSearchParams({ | ||
username: this.options.username, | ||
to: callTo.join(','), | ||
from: callFrom, | ||
clientRequestId | ||
}) | ||
} | ||
axios(config) | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
// API returns CREATED on success | ||
resolve(resp.data) | ||
} else { | ||
reject(resp.data) | ||
}; | ||
}) | ||
} | ||
getNumQueuedCalls(params) { | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}; | ||
}) | ||
} | ||
let options = _.cloneDeep(params); | ||
let _self = this; | ||
getNumQueuedCalls (params) { | ||
const options = _.cloneDeep(params) | ||
const _self = this | ||
// Validate params | ||
let _validateParams = function () { | ||
// Validate params | ||
const _validateParams = function () { | ||
const constraints = { | ||
phoneNumbers: function (value) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
} | ||
} | ||
var constraints = { | ||
phoneNumbers: function (value) { | ||
return null | ||
} | ||
} | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
}; | ||
} | ||
const error = validate(options, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
throw new Error(msg) | ||
} | ||
} | ||
return null; | ||
} | ||
}; | ||
_validateParams() | ||
let error = validate(options, constraints); | ||
if (error) { | ||
var msg = ""; | ||
for (var k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
throw new Error(msg); | ||
} | ||
}; | ||
return new Promise(function (resolve, reject) { | ||
const config = { | ||
method: 'post', | ||
url: `${Common.VOICE_URL}/queueStatus`, | ||
headers: { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
}, | ||
data: JSON.stringify({ | ||
username: _self.options.username, | ||
phoneNumbers: options.phoneNumbers | ||
_validateParams(); | ||
}) | ||
} | ||
axios(config) | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
return new Promise(function (resolve, reject) { | ||
const config = { | ||
method: 'post', | ||
url: `${Common.VOICE_URL}/queueStatus`, | ||
headers: { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
}, | ||
data : JSON.stringify({ | ||
username: _self.options.username, | ||
phoneNumbers: options.phoneNumbers | ||
}) | ||
}; | ||
axios(config) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
// API returns CREATED on success | ||
resolve(resp.data) | ||
} else { | ||
reject(resp.data) | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}) | ||
} | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
// API returns CREATED on success | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
}); | ||
} | ||
uploadMediaFile(params) { | ||
let options = _.cloneDeep(params); | ||
let _self = this; | ||
uploadMediaFile (params) { | ||
const options = _.cloneDeep(params) | ||
const _self = this | ||
// Validate params | ||
let _validateParams = function () { | ||
// Validate params | ||
const _validateParams = function () { | ||
const constraints = { | ||
url: function (value) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
} | ||
} | ||
var constraints = { | ||
url: function (value) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
}; | ||
} | ||
if (!(/^https?\/\//).test(value)) { | ||
return { | ||
format: 'must contain a VALID URL (http(s)://...)' | ||
} | ||
} | ||
if (!(/^https?\:\/\//).test(value)) { | ||
return { | ||
format: 'must contain a VALID URL (http(s)://...)' | ||
}; | ||
} | ||
return null | ||
}, | ||
phoneNumber: function (value) { | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
} | ||
} | ||
return null; | ||
}, | ||
phoneNumber: function (value) { | ||
return null | ||
} | ||
} | ||
if (validate.isEmpty(value)) { | ||
return { | ||
presence: { | ||
message: 'is required' | ||
} | ||
}; | ||
} | ||
const error = validate(options, constraints) | ||
if (error) { | ||
let msg = '' | ||
for (const k in error) { | ||
msg += error[k] + '; ' | ||
} | ||
throw new Error(msg) | ||
} | ||
} | ||
return null; | ||
} | ||
}; | ||
_validateParams() | ||
let error = validate(options, constraints); | ||
if (error) { | ||
var msg = ""; | ||
for (var k in error) { | ||
msg += error[k] + "; "; | ||
} | ||
throw new Error(msg); | ||
} | ||
}; | ||
return new Promise(function (resolve, reject) { | ||
const config = { | ||
method: 'post', | ||
url: `${Common.VOICE_URL}/mediaUpload`, | ||
headers: { | ||
_validateParams(); | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
}, | ||
data: JSON.stringify({ | ||
username: _self.options.username, | ||
url: options.url, | ||
phoneNumber: options.phoneNumber | ||
return new Promise(function (resolve, reject) { | ||
const config = { | ||
method: 'post', | ||
url: `${Common.VOICE_URL}/mediaUpload`, | ||
headers: { | ||
apikey: _self.options.apiKey, | ||
Accept: _self.options.format | ||
}, | ||
data : JSON.stringify({ | ||
username: _self.options.username, | ||
url: options.url, | ||
phoneNumber: options.phoneNumber | ||
}) | ||
}; | ||
}) | ||
} | ||
axios(config) | ||
.then(function (resp) { | ||
const httpStatus = resp.status; | ||
axios(config) | ||
.then(function (resp) { | ||
const httpStatus = resp.status | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
// API returns CREATED on success | ||
resolve(resp.data); | ||
} else { | ||
reject(resp.data); | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error); | ||
}); | ||
}); | ||
} | ||
if (httpStatus === 200 || httpStatus === 201) { | ||
// API returns CREATED on success | ||
resolve(resp.data) | ||
} else { | ||
reject(resp.data) | ||
}; | ||
}) | ||
.catch(function (error) { | ||
return reject(error) | ||
}) | ||
}) | ||
} | ||
} | ||
module.exports = Voice; | ||
module.exports = Voice |
{ | ||
"name": "africastalking", | ||
"version": "0.7.0-beta.1", | ||
"version": "0.7.0", | ||
"description": "Official AfricasTalking node.js API wrapper", | ||
@@ -8,4 +8,9 @@ "main": "index.js", | ||
"test": "nyc mocha ./test --exit --recursive", | ||
"test-windows": "nyc node_modules/mocha/bin/_mocha ./test" | ||
"test-windows": "nyc node_modules/mocha/bin/_mocha ./test", | ||
"lint": "standard --fix", | ||
"pretest": "standard" | ||
}, | ||
"pre-commit": [ | ||
"test" | ||
], | ||
"repository": { | ||
@@ -22,3 +27,4 @@ "type": "git", | ||
"payments", | ||
"airtime" | ||
"airtime", | ||
"mobile_data" | ||
], | ||
@@ -32,10 +38,22 @@ "author": "Africa's Talking", | ||
"engines": { | ||
"node": ">=6" | ||
"node": ">=18" | ||
}, | ||
"standard": { | ||
"ignore": [ | ||
"/example" | ||
], | ||
"globals": [ | ||
"it", | ||
"describe", | ||
"before", | ||
"after", | ||
"context" | ||
] | ||
}, | ||
"dependencies": { | ||
"@hapi/joi": "^16.1.7", | ||
"axios": "^1.6.8", | ||
"axios": "^1.7.2", | ||
"body-parser": "^1.20.2", | ||
"dotenv": "^16.4.5", | ||
"google-libphonenumber": "^3.2.34", | ||
"google-libphonenumber": "^3.2.35", | ||
"lodash": "^4.17.21", | ||
@@ -48,6 +66,8 @@ "querystring": "^0.2.0", | ||
"mocha": "^10.4.0", | ||
"nyc": "^15.1.0", | ||
"nyc": "^17.0.0", | ||
"pre-commit": "^1.2.2", | ||
"should": "^13.2.3", | ||
"supertest": "^6.3.4" | ||
"standard": "^17.1.0", | ||
"supertest": "^7.0.0" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59318
7
19
1698
Updatedaxios@^1.7.2