mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-21 17:17:58 +00:00
write service_provider_sid with alerts
This commit is contained in:
@@ -127,10 +127,22 @@ router.post('/', async(req, res) => {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.debug({call_hook: app.call_hook}, 'creating http client for call hook');
|
logger.debug({call_hook: app.call_hook}, 'creating http client for call hook');
|
||||||
app.requestor = new HttpRequestor(logger, account.account_sid, app.call_hook, account.webhook_secret);
|
app.requestor = new HttpRequestor(
|
||||||
|
logger,
|
||||||
|
account.service_provider_sid,
|
||||||
|
account.account_sid,
|
||||||
|
app.call_hook,
|
||||||
|
account.webhook_secret
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!app.notifier && app.call_status_hook) {
|
if (!app.notifier && app.call_status_hook) {
|
||||||
app.notifier = new HttpRequestor(logger, account.account_sid, app.call_status_hook, account.webhook_secret);
|
app.notifier = new HttpRequestor(
|
||||||
|
logger,
|
||||||
|
account.service_provider_sid,
|
||||||
|
account.account_sid,
|
||||||
|
app.call_status_hook,
|
||||||
|
account.webhook_secret
|
||||||
|
);
|
||||||
logger.debug({call_hook: app.call_hook}, 'creating http client for call status hook');
|
logger.debug({call_hook: app.call_hook}, 'creating http client for call status hook');
|
||||||
}
|
}
|
||||||
else if (!app.notifier) {
|
else if (!app.notifier) {
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ router.post('/:partner', async(req, res) => {
|
|||||||
app.notifier = app.requestor;
|
app.notifier = app.requestor;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
app.requestor = new HttpRequestor(logger, account.account_sid, hook, account.webhook_secret);
|
app.requestor = new HttpRequestor(
|
||||||
|
logger,
|
||||||
|
account.service_provider_sid,
|
||||||
|
account.account_sid,
|
||||||
|
hook,
|
||||||
|
account.webhook_secret
|
||||||
|
);
|
||||||
app.notifier = {request: () => {}};
|
app.notifier = {request: () => {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ module.exports = function(srf, logger) {
|
|||||||
lookupAppByRegex,
|
lookupAppByRegex,
|
||||||
lookupAppBySid,
|
lookupAppBySid,
|
||||||
lookupAppByRealm,
|
lookupAppByRealm,
|
||||||
lookupAppByTeamsTenant
|
lookupAppByTeamsTenant,
|
||||||
|
lookupAccountBySid
|
||||||
} = srf.locals.dbHelpers;
|
} = srf.locals.dbHelpers;
|
||||||
const {
|
const {
|
||||||
writeAlerts,
|
writeAlerts,
|
||||||
@@ -25,7 +26,7 @@ module.exports = function(srf, logger) {
|
|||||||
} = srf.locals;
|
} = srf.locals;
|
||||||
const {lookupAccountDetails} = dbUtils(logger, srf);
|
const {lookupAccountDetails} = dbUtils(logger, srf);
|
||||||
|
|
||||||
function initLocals(req, res, next) {
|
const initLocals = async(req, res, next) => {
|
||||||
const callId = req.get('Call-ID');
|
const callId = req.get('Call-ID');
|
||||||
logger.info({callId}, 'new incoming call');
|
logger.info({callId}, 'new incoming call');
|
||||||
if (!req.has('X-Account-Sid')) {
|
if (!req.has('X-Account-Sid')) {
|
||||||
@@ -34,7 +35,10 @@ module.exports = function(srf, logger) {
|
|||||||
}
|
}
|
||||||
const callSid = req.has('X-Retain-Call-Sid') ? req.get('X-Retain-Call-Sid') : uuidv4();
|
const callSid = req.has('X-Retain-Call-Sid') ? req.get('X-Retain-Call-Sid') : uuidv4();
|
||||||
const account_sid = req.get('X-Account-Sid');
|
const account_sid = req.get('X-Account-Sid');
|
||||||
req.locals = {callSid, account_sid, callId};
|
console.log(`account_sid: ${account_sid}`);
|
||||||
|
const account = await lookupAccountBySid(account_sid);
|
||||||
|
console.log(`account: ${JSON.stringify(account)}`);
|
||||||
|
req.locals = {callSid, account_sid, service_provider_sid: account?.service_provider_sid, callId};
|
||||||
if (req.has('X-Application-Sid')) {
|
if (req.has('X-Application-Sid')) {
|
||||||
const application_sid = req.get('X-Application-Sid');
|
const application_sid = req.get('X-Application-Sid');
|
||||||
logger.debug(`got application from X-Application-Sid header: ${application_sid}`);
|
logger.debug(`got application from X-Application-Sid header: ${application_sid}`);
|
||||||
@@ -44,7 +48,7 @@ module.exports = function(srf, logger) {
|
|||||||
if (req.has('X-MS-Teams-Tenant-FQDN')) req.locals.msTeamsTenant = req.get('X-MS-Teams-Tenant-FQDN');
|
if (req.has('X-MS-Teams-Tenant-FQDN')) req.locals.msTeamsTenant = req.get('X-MS-Teams-Tenant-FQDN');
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
};
|
||||||
|
|
||||||
function createRootSpan(req, res, next) {
|
function createRootSpan(req, res, next) {
|
||||||
const {callId, callSid, account_sid} = req.locals;
|
const {callId, callSid, account_sid} = req.locals;
|
||||||
@@ -161,7 +165,7 @@ module.exports = function(srf, logger) {
|
|||||||
* Given the dialed DID/phone number, retrieve the application to invoke
|
* Given the dialed DID/phone number, retrieve the application to invoke
|
||||||
*/
|
*/
|
||||||
async function retrieveApplication(req, res, next) {
|
async function retrieveApplication(req, res, next) {
|
||||||
const {logger, accountInfo, account_sid, rootSpan} = req.locals;
|
const {logger, accountInfo, service_provider_sid, account_sid, rootSpan} = req.locals;
|
||||||
const {span} = rootSpan.startChildSpan('lookupApplication');
|
const {span} = rootSpan.startChildSpan('lookupApplication');
|
||||||
try {
|
try {
|
||||||
let app;
|
let app;
|
||||||
@@ -231,9 +235,22 @@ module.exports = function(srf, logger) {
|
|||||||
app.call_hook.method = 'WS';
|
app.call_hook.method = 'WS';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
app.requestor = new HttpRequestor(logger, account_sid, app.call_hook, accountInfo.account.webhook_secret);
|
app.requestor = new HttpRequestor(
|
||||||
if (app.call_status_hook) app.notifier = new HttpRequestor(logger, account_sid, app.call_status_hook,
|
logger,
|
||||||
accountInfo.account.webhook_secret);
|
service_provider_sid,
|
||||||
|
account_sid,
|
||||||
|
app.call_hook,
|
||||||
|
accountInfo.account.webhook_secret
|
||||||
|
);
|
||||||
|
if (app.call_status_hook) {
|
||||||
|
app.notifier = new HttpRequestor(
|
||||||
|
logger,
|
||||||
|
service_provider_sid,
|
||||||
|
account_sid,
|
||||||
|
app.call_status_hook,
|
||||||
|
accountInfo.account.webhook_secret
|
||||||
|
);
|
||||||
|
}
|
||||||
else app.notifier = {request: () => {}};
|
else app.notifier = {request: () => {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +333,7 @@ module.exports = function(srf, logger) {
|
|||||||
span?.setAttributes({webhookStatus: err.statusCode});
|
span?.setAttributes({webhookStatus: err.statusCode});
|
||||||
span?.end();
|
span?.end();
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: req.locals.service_provider_sid,
|
||||||
account_sid: req.locals.account_sid,
|
account_sid: req.locals.account_sid,
|
||||||
target_sid: req.locals.callSid,
|
target_sid: req.locals.callSid,
|
||||||
alert_type: AlertType.INVALID_APP_PAYLOAD,
|
alert_type: AlertType.INVALID_APP_PAYLOAD,
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ class CallInfo {
|
|||||||
// inbound call
|
// inbound call
|
||||||
const {app, req} = opts;
|
const {app, req} = opts;
|
||||||
srf = req.srf;
|
srf = req.srf;
|
||||||
this.callSid = req.locals.callSid,
|
this.callSid = req.locals.callSid;
|
||||||
this.accountSid = app.account_sid,
|
this.serviceProviderSid = req.locals.service_provider_sid;
|
||||||
|
this.accountSid = app.account_sid;
|
||||||
this.applicationSid = app.application_sid;
|
this.applicationSid = app.application_sid;
|
||||||
this.from = from || req.callingNumber;
|
this.from = from || req.callingNumber;
|
||||||
this.to = req.calledNumber;
|
this.to = req.calledNumber;
|
||||||
@@ -39,6 +40,7 @@ class CallInfo {
|
|||||||
srf = req.srf;
|
srf = req.srf;
|
||||||
this.callSid = callSid || uuidv4();
|
this.callSid = callSid || uuidv4();
|
||||||
this.parentCallSid = parentCallInfo.callSid;
|
this.parentCallSid = parentCallInfo.callSid;
|
||||||
|
this.serviceProviderSid = parentCallInfo.serviceProviderSid;
|
||||||
this.accountSid = parentCallInfo.accountSid;
|
this.accountSid = parentCallInfo.accountSid;
|
||||||
this.applicationSid = parentCallInfo.applicationSid;
|
this.applicationSid = parentCallInfo.applicationSid;
|
||||||
this.from = from || req.callingNumber;
|
this.from = from || req.callingNumber;
|
||||||
@@ -51,18 +53,20 @@ class CallInfo {
|
|||||||
}
|
}
|
||||||
else if (this.direction === CallDirection.None) {
|
else if (this.direction === CallDirection.None) {
|
||||||
// outbound SMS
|
// outbound SMS
|
||||||
const {messageSid, accountSid, applicationSid, res} = opts;
|
const {messageSid, serviceProviderSid, accountSid, applicationSid, res} = opts;
|
||||||
srf = res.srf;
|
srf = res.srf;
|
||||||
this.messageSid = messageSid;
|
this.messageSid = messageSid;
|
||||||
|
this.serviceProviderSid = serviceProviderSid;
|
||||||
this.accountSid = accountSid;
|
this.accountSid = accountSid;
|
||||||
this.applicationSid = applicationSid;
|
this.applicationSid = applicationSid;
|
||||||
this.res = res;
|
this.res = res;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// outbound call triggered by REST
|
// outbound call triggered by REST
|
||||||
const {req, callSid, accountSid, applicationSid, to, tag} = opts;
|
const {req, callSid, accountSid, serviceProviderSid, applicationSid, to, tag} = opts;
|
||||||
srf = req.srf;
|
srf = req.srf;
|
||||||
this.callSid = callSid;
|
this.callSid = callSid;
|
||||||
|
this.serviceProviderSid = serviceProviderSid;
|
||||||
this.accountSid = accountSid;
|
this.accountSid = accountSid;
|
||||||
this.applicationSid = applicationSid;
|
this.applicationSid = applicationSid;
|
||||||
this.callStatus = CallStatus.Trying,
|
this.callStatus = CallStatus.Trying,
|
||||||
|
|||||||
@@ -201,6 +201,12 @@ class CallSession extends Emitter {
|
|||||||
return this.direction === CallDirection.Inbound && this.res.finalResponseSent;
|
return this.direction === CallDirection.Inbound && this.res.finalResponseSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the account sid
|
||||||
|
*/
|
||||||
|
get serviceProviderSid() {
|
||||||
|
return this.callInfo.serviceProviderSid;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* returns the account sid
|
* returns the account sid
|
||||||
*/
|
*/
|
||||||
@@ -520,6 +526,7 @@ class CallSession extends Emitter {
|
|||||||
this.logger.info({err}, `malformed google service_key provisioned for account ${sid}`);
|
this.logger.info({err}, `malformed google service_key provisioned for account ${sid}`);
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
alert_type: AlertType.TTS_FAILURE,
|
alert_type: AlertType.TTS_FAILURE,
|
||||||
|
service_provider_sid: this.serviceProviderSid,
|
||||||
account_sid: this.accountSid,
|
account_sid: this.accountSid,
|
||||||
vendor
|
vendor
|
||||||
}).catch((err) => this.logger.error({err}, 'Error writing tts alert'));
|
}).catch((err) => this.logger.error({err}, 'Error writing tts alert'));
|
||||||
@@ -550,6 +557,7 @@ class CallSession extends Emitter {
|
|||||||
else {
|
else {
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
alert_type: AlertType.STT_NOT_PROVISIONED,
|
alert_type: AlertType.STT_NOT_PROVISIONED,
|
||||||
|
service_provider_sid: this.serviceProviderSid,
|
||||||
account_sid: this.accountSid,
|
account_sid: this.accountSid,
|
||||||
vendor
|
vendor
|
||||||
}).catch((err) => this.logger.error({err}, 'Error writing tts alert'));
|
}).catch((err) => this.logger.error({err}, 'Error writing tts alert'));
|
||||||
@@ -1382,8 +1390,13 @@ class CallSession extends Emitter {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.logger.info({accountSid: this.accountSid, webhook: r[0]}, 'performQueueWebhook: webhook found');
|
this.logger.info({accountSid: this.accountSid, webhook: r[0]}, 'performQueueWebhook: webhook found');
|
||||||
this.queueEventHookRequestor = new HttpRequestor(this.logger, this.accountSid,
|
this.queueEventHookRequestor = new HttpRequestor(
|
||||||
r[0], this.webhook_secret);
|
this.logger,
|
||||||
|
this.serviceProviderSid,
|
||||||
|
this.accountSid,
|
||||||
|
r[0],
|
||||||
|
this.webhook_secret
|
||||||
|
);
|
||||||
this.queueEventHook = r[0];
|
this.queueEventHook = r[0];
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ class TaskGather extends Task {
|
|||||||
const {writeAlerts, AlertType} = cs.srf.locals;
|
const {writeAlerts, AlertType} = cs.srf.locals;
|
||||||
this.logger.info(`TaskGather:exec - ERROR stt using ${this.vendor} requested but creds not supplied`);
|
this.logger.info(`TaskGather:exec - ERROR stt using ${this.vendor} requested but creds not supplied`);
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: cs.serviceProviderSid,
|
||||||
account_sid: cs.accountSid,
|
account_sid: cs.accountSid,
|
||||||
alert_type: AlertType.STT_NOT_PROVISIONED,
|
alert_type: AlertType.STT_NOT_PROVISIONED,
|
||||||
vendor: this.vendor
|
vendor: this.vendor
|
||||||
@@ -399,6 +400,7 @@ class TaskGather extends Task {
|
|||||||
const {writeAlerts, AlertType} = this.cs.srf.locals;
|
const {writeAlerts, AlertType} = this.cs.srf.locals;
|
||||||
this.logger.error(err, 'TaskGather:_startTranscribing error');
|
this.logger.error(err, 'TaskGather:_startTranscribing error');
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: this.cs.serviceProviderSid,
|
||||||
account_sid: this.cs.accountSid,
|
account_sid: this.cs.accountSid,
|
||||||
alert_type: AlertType.STT_FAILURE,
|
alert_type: AlertType.STT_FAILURE,
|
||||||
vendor: this.vendor,
|
vendor: this.vendor,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class TaskSay extends Task {
|
|||||||
try {
|
try {
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: cs.serviceProviderSid,
|
||||||
account_sid: cs.accountSid,
|
account_sid: cs.accountSid,
|
||||||
alert_type: AlertType.TTS_NOT_PROVISIONED,
|
alert_type: AlertType.TTS_NOT_PROVISIONED,
|
||||||
vendor
|
vendor
|
||||||
@@ -92,6 +93,7 @@ class TaskSay extends Task {
|
|||||||
this.logger.info({err}, 'Error synthesizing tts');
|
this.logger.info({err}, 'Error synthesizing tts');
|
||||||
span.end();
|
span.end();
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: cs.serviceProviderSid,
|
||||||
account_sid: cs.accountSid,
|
account_sid: cs.accountSid,
|
||||||
alert_type: AlertType.TTS_NOT_PROVISIONED,
|
alert_type: AlertType.TTS_NOT_PROVISIONED,
|
||||||
vendor,
|
vendor,
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class TaskTranscribe extends Task {
|
|||||||
const {writeAlerts, AlertType} = cs.srf.locals;
|
const {writeAlerts, AlertType} = cs.srf.locals;
|
||||||
this.logger.info(`TaskTranscribe:exec - ERROR stt using ${this.vendor} requested but creds not supplied`);
|
this.logger.info(`TaskTranscribe:exec - ERROR stt using ${this.vendor} requested but creds not supplied`);
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: cs.serviceProviderSid,
|
||||||
account_sid: cs.accountSid,
|
account_sid: cs.accountSid,
|
||||||
alert_type: AlertType.STT_NOT_PROVISIONED,
|
alert_type: AlertType.STT_NOT_PROVISIONED,
|
||||||
vendor: this.vendor
|
vendor: this.vendor
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ module.exports = (logger) => {
|
|||||||
task.emit(AmdEvents.Error, err);
|
task.emit(AmdEvents.Error, err);
|
||||||
logger.error(err, 'amd:_startTranscribing error');
|
logger.error(err, 'amd:_startTranscribing error');
|
||||||
writeAlerts({
|
writeAlerts({
|
||||||
|
service_provider_sid: cs.serviceProviderSid,
|
||||||
account_sid: cs.accountSid,
|
account_sid: cs.accountSid,
|
||||||
alert_type: AlertType.STT_FAILURE,
|
alert_type: AlertType.STT_FAILURE,
|
||||||
vendor: vendor,
|
vendor: vendor,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const timeSeries = require('@jambonz/time-series');
|
|||||||
let alerter ;
|
let alerter ;
|
||||||
|
|
||||||
class BaseRequestor extends Emitter {
|
class BaseRequestor extends Emitter {
|
||||||
constructor(logger, account_sid, hook, secret) {
|
constructor(logger, service_provider_sid, account_sid, hook, secret) {
|
||||||
super();
|
super();
|
||||||
assert(typeof hook === 'object');
|
assert(typeof hook === 'object');
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ class BaseRequestor extends Emitter {
|
|||||||
this.username = hook.username;
|
this.username = hook.username;
|
||||||
this.password = hook.password;
|
this.password = hook.password;
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
|
this.service_provider_sid = service_provider_sid;
|
||||||
this.account_sid = account_sid;
|
this.account_sid = account_sid;
|
||||||
|
|
||||||
const {stats} = require('../../').srf.locals;
|
const {stats} = require('../../').srf.locals;
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ function basicAuth(username, password) {
|
|||||||
|
|
||||||
|
|
||||||
class HttpRequestor extends BaseRequestor {
|
class HttpRequestor extends BaseRequestor {
|
||||||
constructor(logger, account_sid, hook, secret) {
|
constructor(logger, service_provider_sid, account_sid, hook, secret) {
|
||||||
super(logger, account_sid, hook, secret);
|
super(logger, service_provider_sid, account_sid, hook, secret);
|
||||||
|
|
||||||
this.method = hook.method || 'POST';
|
this.method = hook.method || 'POST';
|
||||||
this.authHeader = basicAuth(hook.username, hook.password);
|
this.authHeader = basicAuth(hook.username, hook.password);
|
||||||
@@ -142,7 +142,7 @@ class HttpRequestor extends BaseRequestor {
|
|||||||
this.logger.error({err, baseUrl: this.baseUrl, url},
|
this.logger.error({err, baseUrl: this.baseUrl, url},
|
||||||
'web callback returned unexpected error');
|
'web callback returned unexpected error');
|
||||||
}
|
}
|
||||||
let opts = {account_sid: this.account_sid};
|
let opts = {account_sid: this.account_sid, service_provider_sid: this.service_provider_sid};
|
||||||
if (err.code === 'ECONNREFUSED') {
|
if (err.code === 'ECONNREFUSED') {
|
||||||
opts = {...opts, alert_type: this.Alerter.AlertType.WEBHOOK_CONNECTION_FAILURE, url};
|
opts = {...opts, alert_type: this.Alerter.AlertType.WEBHOOK_CONNECTION_FAILURE, url};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
const bent = require('bent');
|
|
||||||
const parseUrl = require('parse-url');
|
|
||||||
const assert = require('assert');
|
|
||||||
const snakeCaseKeys = require('./snakecase-keys');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const timeSeries = require('@jambonz/time-series');
|
|
||||||
let alerter ;
|
|
||||||
|
|
||||||
const toBase64 = (str) => Buffer.from(str || '', 'utf8').toString('base64');
|
|
||||||
|
|
||||||
function computeSignature(payload, timestamp, secret) {
|
|
||||||
assert(secret);
|
|
||||||
const data = `${timestamp}.${JSON.stringify(payload)}`;
|
|
||||||
return crypto
|
|
||||||
.createHmac('sha256', secret)
|
|
||||||
.update(data, 'utf8')
|
|
||||||
.digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateSigHeader(payload, secret) {
|
|
||||||
const timestamp = Math.floor(Date.now() / 1000);
|
|
||||||
const signature = computeSignature(payload, timestamp, secret);
|
|
||||||
const scheme = 'v1';
|
|
||||||
return {
|
|
||||||
'Jambonz-Signature': `t=${timestamp},${scheme}=${signature}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function basicAuth(username, password) {
|
|
||||||
if (!username || !password) return {};
|
|
||||||
const creds = `${username}:${password || ''}`;
|
|
||||||
const header = `Basic ${toBase64(creds)}`;
|
|
||||||
return {Authorization: header};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRelativeUrl(u) {
|
|
||||||
return typeof u === 'string' && u.startsWith('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAbsoluteUrl(u) {
|
|
||||||
return typeof u === 'string' &&
|
|
||||||
u.startsWith('https://') || u.startsWith('http://');
|
|
||||||
}
|
|
||||||
|
|
||||||
class Requestor {
|
|
||||||
constructor(logger, account_sid, hook, secret) {
|
|
||||||
assert(typeof hook === 'object');
|
|
||||||
|
|
||||||
this.logger = logger;
|
|
||||||
this.url = hook.url;
|
|
||||||
this.method = hook.method || 'POST';
|
|
||||||
this.authHeader = basicAuth(hook.username, hook.password);
|
|
||||||
|
|
||||||
const u = parseUrl(this.url);
|
|
||||||
const myPort = u.port ? `:${u.port}` : '';
|
|
||||||
const baseUrl = this._baseUrl = `${u.protocol}://${u.resource}${myPort}`;
|
|
||||||
|
|
||||||
this.get = bent(baseUrl, 'GET', 'buffer', 200, 201);
|
|
||||||
this.post = bent(baseUrl, 'POST', 'buffer', 200, 201);
|
|
||||||
|
|
||||||
this.username = hook.username;
|
|
||||||
this.password = hook.password;
|
|
||||||
this.secret = secret;
|
|
||||||
this.account_sid = account_sid;
|
|
||||||
|
|
||||||
assert(isAbsoluteUrl(this.url));
|
|
||||||
assert(['GET', 'POST'].includes(this.method));
|
|
||||||
|
|
||||||
const {stats} = require('../../').srf.locals;
|
|
||||||
this.stats = stats;
|
|
||||||
|
|
||||||
if (!alerter) {
|
|
||||||
alerter = timeSeries(logger, {
|
|
||||||
host: process.env.JAMBONES_TIME_SERIES_HOST,
|
|
||||||
commitSize: 50,
|
|
||||||
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseUrl() {
|
|
||||||
return this._baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make an HTTP request.
|
|
||||||
* All requests use json bodies.
|
|
||||||
* All requests expect a 200 statusCode on success
|
|
||||||
* @param {object|string} hook - may be a absolute or relative url, or an object
|
|
||||||
* @param {string} [hook.url] - an absolute or relative url
|
|
||||||
* @param {string} [hook.method] - 'GET' or 'POST'
|
|
||||||
* @param {string} [hook.username] - if basic auth is protecting the endpoint
|
|
||||||
* @param {string} [hook.password] - if basic auth is protecting the endpoint
|
|
||||||
* @param {object} [params] - request parameters
|
|
||||||
*/
|
|
||||||
async request(hook, params) {
|
|
||||||
const payload = params ? snakeCaseKeys(params, ['customerData', 'sip']) : null;
|
|
||||||
const url = hook.url || hook;
|
|
||||||
const method = hook.method || 'POST';
|
|
||||||
|
|
||||||
assert.ok(url, 'Requestor:request url was not provided');
|
|
||||||
assert.ok, (['GET', 'POST'].includes(method), `Requestor:request method must be 'GET' or 'POST' not ${method}`);
|
|
||||||
const {url: urlInfo = hook, method: methodInfo = 'POST'} = hook; // mask user/pass
|
|
||||||
this.logger.debug({url: urlInfo, method: methodInfo, payload}, `Requestor:request ${method} ${url}`);
|
|
||||||
const startAt = process.hrtime();
|
|
||||||
|
|
||||||
let buf;
|
|
||||||
try {
|
|
||||||
const sigHeader = generateSigHeader(payload, this.secret);
|
|
||||||
const headers = {...sigHeader, ...this.authHeader};
|
|
||||||
//this.logger.info({url, headers}, 'send webhook');
|
|
||||||
buf = isRelativeUrl(url) ?
|
|
||||||
await this.post(url, payload, headers) :
|
|
||||||
await bent(method, 'buffer', 200, 201, 202)(url, payload, headers);
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.error({err, secret: this.secret, baseUrl: this.baseUrl, url, statusCode: err.statusCode},
|
|
||||||
`web callback returned unexpected error code ${err.statusCode}`);
|
|
||||||
let opts = {account_sid: this.account_sid};
|
|
||||||
if (err.code === 'ECONNREFUSED') {
|
|
||||||
opts = {...opts, alert_type: alerter.AlertType.WEBHOOK_CONNECTION_FAILURE, url};
|
|
||||||
}
|
|
||||||
else if (err.name === 'StatusError') {
|
|
||||||
opts = {...opts, alert_type: alerter.AlertType.WEBHOOK_STATUS_FAILURE, url, status: err.statusCode};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
opts = {...opts, alert_type: alerter.AlertType.WEBHOOK_CONNECTION_FAILURE, url, detail: err.message};
|
|
||||||
}
|
|
||||||
alerter.writeAlerts(opts).catch((err) => this.logger.info({err, opts}, 'Error writing alert'));
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
const diff = process.hrtime(startAt);
|
|
||||||
const time = diff[0] * 1e3 + diff[1] * 1e-6;
|
|
||||||
const rtt = time.toFixed(0);
|
|
||||||
if (buf) this.stats.histogram('app.hook.response_time', rtt, ['hook_type:app']);
|
|
||||||
|
|
||||||
if (buf && buf.toString().length > 0) {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(buf.toString());
|
|
||||||
this.logger.info({response: json}, `Requestor:request ${method} ${url} succeeded in ${rtt}ms`);
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
//this.logger.debug({err, url, method}, `Requestor:request returned non-JSON content: '${buf.toString()}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Requestor;
|
|
||||||
@@ -9,8 +9,8 @@ const MAX_RECONNECTS = 5;
|
|||||||
const RESPONSE_TIMEOUT_MS = process.env.JAMBONES_WS_API_MSG_RESPONSE_TIMEOUT || 5000;
|
const RESPONSE_TIMEOUT_MS = process.env.JAMBONES_WS_API_MSG_RESPONSE_TIMEOUT || 5000;
|
||||||
|
|
||||||
class WsRequestor extends BaseRequestor {
|
class WsRequestor extends BaseRequestor {
|
||||||
constructor(logger, account_sid, hook, secret) {
|
constructor(logger, service_provider_sid, account_sid, hook, secret) {
|
||||||
super(logger, account_sid, hook, secret);
|
super(logger, service_provider_sid, account_sid, hook, secret);
|
||||||
this.connections = 0;
|
this.connections = 0;
|
||||||
this.messagesInFlight = new Map();
|
this.messagesInFlight = new Map();
|
||||||
this.maliciousClient = false;
|
this.maliciousClient = false;
|
||||||
@@ -54,7 +54,7 @@ class WsRequestor extends BaseRequestor {
|
|||||||
/* if we have an absolute url, and it is http then do a standard webhook */
|
/* if we have an absolute url, and it is http then do a standard webhook */
|
||||||
if (this._isAbsoluteUrl(url) && url.startsWith('http')) {
|
if (this._isAbsoluteUrl(url) && url.startsWith('http')) {
|
||||||
this.logger.debug({hook}, 'WsRequestor: sending a webhook (HTTP)');
|
this.logger.debug({hook}, 'WsRequestor: sending a webhook (HTTP)');
|
||||||
const requestor = new HttpRequestor(this.logger, this.account_sid, hook, this.secret);
|
const requestor = new HttpRequestor(this.logger, this.service_provider_sid, this.account_sid, hook, this.secret);
|
||||||
return requestor.request(type, hook, params, httpHeaders);
|
return requestor.request(type, hook, params, httpHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"@jambonz/http-health-check": "^0.0.1",
|
"@jambonz/http-health-check": "^0.0.1",
|
||||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
"@jambonz/realtimedb-helpers": "^0.4.29",
|
||||||
"@jambonz/stats-collector": "^0.1.6",
|
"@jambonz/stats-collector": "^0.1.6",
|
||||||
"@jambonz/time-series": "^0.1.12",
|
"@jambonz/time-series": "^0.2.0",
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
"@opentelemetry/exporter-jaeger": "^1.3.1",
|
"@opentelemetry/exporter-jaeger": "^1.3.1",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user