mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-20 16:50:39 +00:00
added support for conference verb
This commit is contained in:
@@ -5,7 +5,7 @@ const moment = require('moment');
|
||||
const assert = require('assert');
|
||||
const sessionTracker = require('./session-tracker');
|
||||
const makeTask = require('../tasks/make_task');
|
||||
const normalizeJamones = require('../utils/normalize-jamones');
|
||||
const normalizeJambones = require('../utils/normalize-jambones');
|
||||
const listTaskNames = require('../utils/summarize-tasks');
|
||||
const BADPRECONDITIONS = 'preconditions not met';
|
||||
|
||||
@@ -43,7 +43,8 @@ class CallSession extends Emitter {
|
||||
|
||||
this.tmpFiles = new Set();
|
||||
|
||||
sessionTracker.add(this.callSid, this);
|
||||
// if this is a ConfirmSession
|
||||
if (!this.isConfirmCallSession) sessionTracker.add(this.callSid, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +144,27 @@ class CallSession extends Emitter {
|
||||
return this.direction === CallDirection.Inbound && this.res.finalResponseSent;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the account sid
|
||||
*/
|
||||
get accountSid() {
|
||||
return this.callInfo.accountSid;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session was transferred from another server
|
||||
*/
|
||||
get isTransferredCall() {
|
||||
return this.application.transferredCall === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session is a ConfirmCallSession
|
||||
*/
|
||||
get isConfirmCallSession() {
|
||||
return this.constructor.name === 'ConfirmCallSession';
|
||||
}
|
||||
|
||||
/**
|
||||
* execute the tasks in the CallSession. The tasks are executed in sequence until
|
||||
* they complete, or the caller hangs up.
|
||||
@@ -178,7 +200,7 @@ class CallSession extends Emitter {
|
||||
this._onTasksDone();
|
||||
this._clearResources();
|
||||
|
||||
sessionTracker.remove(this.callSid);
|
||||
if (!this.isConfirmCallSession) sessionTracker.remove(this.callSid);
|
||||
}
|
||||
|
||||
trackTmpFile(path) {
|
||||
@@ -224,7 +246,7 @@ class CallSession extends Emitter {
|
||||
this.logger.debug('CallSession:_callReleased - caller hung up');
|
||||
this.callGone = true;
|
||||
if (this.currentTask) {
|
||||
this.currentTask.kill();
|
||||
this.currentTask.kill(this);
|
||||
this.currentTask = null;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +287,7 @@ class CallSession extends Emitter {
|
||||
const tasks = await this.requestor.request(opts.call_hook, this.callInfo);
|
||||
if (tasks && tasks.length > 0) {
|
||||
this.logger.info({tasks: listTaskNames(tasks)}, 'CallSession:updateCall new task list');
|
||||
this.replaceApplication(normalizeJamones(this.logger, tasks).map((tdata) => makeTask(this.logger, tdata)));
|
||||
this.replaceApplication(normalizeJambones(this.logger, tasks).map((tdata) => makeTask(this.logger, tdata)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,15 +336,15 @@ class CallSession extends Emitter {
|
||||
if (typeof whisper === 'string' || (typeof whisper === 'object' && whisper.url)) {
|
||||
// retrieve a url
|
||||
const json = await this.requestor(opts.call_hook, this.callInfo);
|
||||
tasks = normalizeJamones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
|
||||
tasks = normalizeJambones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
|
||||
}
|
||||
else if (Array.isArray(whisper)) {
|
||||
// an inline array of tasks
|
||||
tasks = normalizeJamones(this.logger, whisper).map((tdata) => makeTask(this.logger, tdata));
|
||||
tasks = normalizeJambones(this.logger, whisper).map((tdata) => makeTask(this.logger, tdata));
|
||||
}
|
||||
else if (typeof whisper === 'object') {
|
||||
// a single task
|
||||
tasks = normalizeJamones(this.logger, [whisper]).map((tdata) => makeTask(this.logger, tdata));
|
||||
tasks = normalizeJambones(this.logger, [whisper]).map((tdata) => makeTask(this.logger, tdata));
|
||||
}
|
||||
else {
|
||||
this.logger.info({opts}, 'CallSession:_lccWhisper invalid options were provided');
|
||||
@@ -406,6 +428,18 @@ class CallSession extends Emitter {
|
||||
this.currentTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
kill() {
|
||||
if (this.isConfirmCallSession) this.logger.debug('CallSession:kill (ConfirmSession)');
|
||||
else this.logger.info('CallSession:kill');
|
||||
if (this.currentTask) {
|
||||
this.currentTask.kill();
|
||||
this.currentTask = null;
|
||||
}
|
||||
this.tasks = [];
|
||||
this.taskIdx = 0;
|
||||
}
|
||||
|
||||
_evaluatePreconditions(task) {
|
||||
switch (task.preconditions) {
|
||||
case TaskPreconditions.None:
|
||||
@@ -426,19 +460,9 @@ class CallSession extends Emitter {
|
||||
* @param {Task} task - task to be executed
|
||||
*/
|
||||
async _evalEndpointPrecondition(task) {
|
||||
this.logger.debug('_evalEndpointPrecondition');
|
||||
if (this.callGone) new Error(`${BADPRECONDITIONS}: call gone`);
|
||||
|
||||
const answerCall = async() => {
|
||||
const uas = await this.srf.createUAS(this.req, this.res, {localSdp: this.ep.local.sdp});
|
||||
uas.on('destroy', this._callerHungup.bind(this));
|
||||
uas.callSid = this.callSid;
|
||||
uas.connectTime = moment();
|
||||
this.dlg = uas;
|
||||
this.wrapDialog(this.dlg);
|
||||
this.emit('callStatusChange', {sipStatus: 200, callStatus: CallStatus.InProgress});
|
||||
this.logger.debug('CallSession:_evalEndpointPrecondition - answered call');
|
||||
};
|
||||
|
||||
if (this.ep) {
|
||||
if (!task.earlyMedia || this.dlg) return this.ep;
|
||||
|
||||
@@ -454,12 +478,15 @@ class CallSession extends Emitter {
|
||||
ep.cs = this;
|
||||
this.ep = ep;
|
||||
|
||||
this.logger.debug('allocated endpoint');
|
||||
|
||||
if (this.direction === CallDirection.Inbound) {
|
||||
if (task.earlyMedia && !this.req.finalResponseSent) {
|
||||
this.res.send(183, {body: ep.local.sdp});
|
||||
return ep;
|
||||
}
|
||||
this.propagateAnswer();
|
||||
this.logger.debug('propogating answer');
|
||||
await this.propagateAnswer();
|
||||
}
|
||||
else {
|
||||
// outbound call TODO
|
||||
@@ -495,6 +522,23 @@ class CallSession extends Emitter {
|
||||
return {req: this.req, res: this.res};
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard the current endpoint and allocate a new one, connecting the dialog to it.
|
||||
* This is used, for instance, from the Conference verb when a caller has been
|
||||
* kicked out of conference when a moderator leaves -- the endpoint is destroyed
|
||||
* as well, but the app may want to continue on with other actions
|
||||
*/
|
||||
async replaceEndpoint() {
|
||||
if (!this.dlg) {
|
||||
this.logger.error('CallSession:replaceEndpoint cannot be called without stable dlg');
|
||||
return;
|
||||
}
|
||||
this.ep = await this.ms.createEndpoint({remoteSdp: this.dlg.remote.sdp});
|
||||
await this.dlg.modify(this.ep.local.sdp);
|
||||
this.logger.debug('CallSession:replaceEndpoint completed');
|
||||
return this.ep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hang up the call and free the media endpoint
|
||||
*/
|
||||
@@ -543,13 +587,14 @@ class CallSession extends Emitter {
|
||||
|
||||
/**
|
||||
* Answer the call, if it has not already been answered.
|
||||
*
|
||||
*
|
||||
* NB: This should be the one and only place we generate 200 OK to incoming INVITEs
|
||||
*/
|
||||
async propagateAnswer() {
|
||||
if (!this.dlg) {
|
||||
assert(this.ep);
|
||||
this.dlg = await this.srf.createUAS(this.req, this.res, {localSdp: this.ep.local.sdp});
|
||||
this.logger.debug('answered call');
|
||||
this.dlg.on('destroy', this._callerHungup.bind(this));
|
||||
this.wrapDialog(this.dlg);
|
||||
this.dlg.callSid = this.callSid;
|
||||
@@ -590,6 +635,45 @@ class CallSession extends Emitter {
|
||||
return {ms: this.ms, ep: this.ep};
|
||||
}
|
||||
|
||||
/**
|
||||
* A conference that the current task is waiting on has just started
|
||||
* @param {*} opts
|
||||
*/
|
||||
notifyStartConference(opts) {
|
||||
if (this.currentTask && typeof this.currentTask.notifyStartConference === 'function') {
|
||||
this.currentTask.notifyStartConference(this, opts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer the call to another feature server
|
||||
* @param {uri} sip uri to refer the call to
|
||||
*/
|
||||
async referCall(referTo) {
|
||||
assert (this.hasStableDialog);
|
||||
|
||||
const res = await this.dlg.request({
|
||||
method: 'REFER',
|
||||
headers: {
|
||||
'Refer-To': referTo,
|
||||
'Referred-By': `sip:${this.srf.locals.localSipAddress}`
|
||||
}
|
||||
});
|
||||
return [200, 202].includes(res.status);
|
||||
}
|
||||
|
||||
getRemainingTaskData() {
|
||||
const tasks = [...this.tasks];
|
||||
tasks.unshift(this.currentTask);
|
||||
const remainingTasks = [];
|
||||
for (const task of tasks) {
|
||||
const o = {};
|
||||
o[task.name] = task.toJSON();
|
||||
remainingTasks.push(o);
|
||||
}
|
||||
return remainingTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this whenever we answer the A leg, creating a dialog
|
||||
* It wraps the 'destroy' method such that if we hang up the A leg
|
||||
|
||||
Reference in New Issue
Block a user