diff --git a/lib/tasks/make_task.js b/lib/tasks/make_task.js index 0803d4fd..7b6eb89f 100644 --- a/lib/tasks/make_task.js +++ b/lib/tasks/make_task.js @@ -17,8 +17,10 @@ function makeTask(logger, obj, parent) { case TaskName.SipDecline: const TaskSipDecline = require('./sip_decline'); return new TaskSipDecline(logger, data, parent); + case TaskName.SipRefer: + const TaskSipRefer = require('./sip_refer'); + return new TaskSipRefer(logger, data, parent); case TaskName.Conference: - logger.debug({data}, 'Conference verb'); const TaskConference = require('./conference'); return new TaskConference(logger, data, parent); case TaskName.Dial: diff --git a/lib/tasks/sip_refer.js b/lib/tasks/sip_refer.js new file mode 100644 index 00000000..9a42fc1b --- /dev/null +++ b/lib/tasks/sip_refer.js @@ -0,0 +1,101 @@ +const Task = require('./task'); +const {TaskName, TaskPreconditions} = require('../utils/constants'); +const {parseUri} = require('drachtio-srf'); + +/** + * sends a sip REFER to transfer the existing call + */ +class TaskSipRefer extends Task { + constructor(logger, opts) { + super(logger, opts); + this.preconditions = TaskPreconditions.StableCall; + + this.referTo = this.data.referTo; + this.referredBy = this.data.referredBy; + this.headers = this.data.headers || {}; + this.eventHook = this.data.eventHook; + } + + get name() { return TaskName.SipRefer; } + + async exec(cs) { + super.exec(cs); + const {dlg} = cs; + const {referTo, referredBy} = this._normalizeReferHeaders(cs, dlg); + + try { + this.notifyHandler = this._handleNotify.bind(this, cs, dlg); + dlg.on('notify', this.notifyHandler); + const response = await dlg.request({ + method: 'REFER', + headers: { + ...this.headers, + 'Refer-To': referTo, + 'Referred-By': referredBy + } + }); + this.referStatus = response.status; + this.logger.info(`TaskSipRefer:exec - received ${this.referStatus} to REFER`); + + /* if we fail, fall through to next verb. If success, we should get BYE from far end */ + if (this.referStatus === 202) { + await this.awaitTaskDone(); + } + else await this.performAction({refer_status: this.referStatus}); + } catch (err) { + this.logger.info({err}, 'TaskSipRefer:exec - error sending REFER'); + } + } + + async kill(cs) { + super.kill(cs); + const {dlg} = cs; + dlg.off('notify', this.notifyHandler); + } + + async _handleNotify(cs, dlg, req, res) { + res.send(200); + + const contentType = req.get('Content-Type'); + this.logger.debug({body: req.body}, `TaskSipRefer:_handleNotify got ${contentType}`); + + if (contentType === 'message/sipfrag') { + const arr = /SIP\/2\.0\s+(\d+)/.exec(req.body); + if (arr) { + const status = arr[1]; + this.logger.debug(`TaskSipRefer:_handleNotify: call got status ${status}`); + if (this.eventHook) { + await cs.requestor.request(this.eventHook, {event: 'transfer-status', call_status: status}); + } + if (status >= 200) { + await this.performAction({refer_status: 202, final_referred_call_status: status}); + this.notifyTaskDone(); + } + } + } + } + + _normalizeReferHeaders(cs, dlg) { + let {referTo, referredBy} = this; + + /* get IP address of the SBC to use as hostname if needed */ + const {host} = parseUri(dlg.remote.uri); + + if (!referTo.startsWith('<') && !referTo.startsWith('sip') && !referTo.startsWith('"')) { + /* they may have only provided a phone number/user */ + referTo = `sip:${referTo}@${host}`; + } + if (!referredBy) { + /* default */ + referredBy = cs.req?.callingNumber || dlg.local.uri; + this.logger.info({referredBy}, 'setting referredby'); + } + if (!referredBy.startsWith('<') && !referredBy.startsWith('sip') && !referredBy.startsWith('"')) { + /* they may have only provided a phone number/user */ + referredBy = `sip:${referredBy}@${host}`; + } + return {referTo, referredBy}; + } +} + +module.exports = TaskSipRefer; diff --git a/lib/tasks/specs.json b/lib/tasks/specs.json index 93aa145e..d7aeb033 100644 --- a/lib/tasks/specs.json +++ b/lib/tasks/specs.json @@ -9,6 +9,18 @@ "status" ] }, + "sip:refer": { + "properties": { + "referTo": "string", + "referredBy": "string", + "headers": "object", + "actionHook": "object|string", + "eventHook": "object|string" + }, + "required": [ + "referTo" + ] + }, "dequeue": { "properties": { "name": "string", diff --git a/lib/utils/constants.json b/lib/utils/constants.json index 652a5d13..ec8bea45 100644 --- a/lib/utils/constants.json +++ b/lib/utils/constants.json @@ -18,6 +18,7 @@ "Redirect": "redirect", "RestDial": "rest:dial", "SipDecline": "sip:decline", + "SipRefer": "sip:refer", "SipNotify": "sip:notify", "SipRedirect": "sip:redirect", "Say": "say",