fix(status): don't re-send pre-transfer call statuses after a transfer (#1584)

A call transferred between feature servers arrives at the receiving server as a
fresh INVITE, so the session runs through trying, ringing, early-media and
answered again as it accepts the REFER. The application was already told all of
that by the server that first handled the call, so it sees a second round of
events for a call it believes is long since answered.

Skip only the application-facing status callback for those four statuses when
the session is a transferred call. callInfo, the redis call record and
record-all-calls still run exactly as before, so the call record stays accurate
and answering a transferred call still starts recording.

Fixes #1392

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Rehan Sanjay VenkatesanandClaude Opus 5 authored and GitHub committed 2026-09-09 14:43:32 +01:00
1 parent 6cc3dc45f2
commit a4e7afac83
2 files changed
+131 -1

No files matched your search

+28 -1
View File
@@ -482,6 +482,24 @@ class CallSession extends Emitter {
return this.application.transferredCall === true;
}
/**
* returns true if this status was already reported to the application by the feature
* server that handled the call before it was transferred here.
*
* A transferred call arrives as a fresh INVITE, so this session runs through trying,
* ringing and answered again as it accepts the REFER. The application has already seen
* all of those from the original server, so sending them a second time looks like
* duplicate events for a call it believes is long since answered.
*/
_isStatusAlreadyReportedBeforeTransfer(callStatus) {
return this.isTransferredCall && [
CallStatus.Trying,
CallStatus.Ringing,
CallStatus.EarlyMedia,
CallStatus.InProgress
].includes(callStatus);
}
/**
* returns true if this session is an inbound call session
*/
@@ -3151,7 +3169,16 @@ Duration=${duration} `
sipReasonHeader ?? reasonHeaderFromSipMessage(msg));
if (typeof duration === 'number') this.callInfo.duration = duration;
if (headers) this.callInfo.sipHeaders = headers;
this.executeStatusCallback(callStatus, sipStatus);
/* callInfo and the redis call record are still updated below; only the
application-facing notification is skipped */
if (this._isStatusAlreadyReportedBeforeTransfer(callStatus)) {
this.logger.debug({callStatus},
'CallSession:_notifyCallStatusChange - suppressing status already sent before transfer');
}
else {
this.executeStatusCallback(callStatus, sipStatus);
}
// update calls db
//this.logger.debug(`updating redis with ${JSON.stringify(this.callInfo)}`);