diff --git a/src/RTCSession/ReferSubscriber.js b/src/RTCSession/ReferSubscriber.js index 91d4065c..54a2e681 100644 --- a/src/RTCSession/ReferSubscriber.js +++ b/src/RTCSession/ReferSubscriber.js @@ -35,9 +35,19 @@ module.exports = class ReferSubscriber extends EventEmitter { let replaces = null; if (options.replaces) { - replaces = options.replaces._request.call_id; - replaces += `;to-tag=${options.replaces._to_tag}`; - replaces += `;from-tag=${options.replaces._from_tag}`; + const dialog = options.replaces._dialog; + + // RFC 3891 section 3: to-tag is the remote tag and from-tag the local + // tag of the dialog to replace, whoever sent the initial INVITE. + if (dialog) { + replaces = dialog.id.call_id; + replaces += `;to-tag=${dialog.id.remote_tag}`; + replaces += `;from-tag=${dialog.id.local_tag}`; + } else { + replaces = options.replaces._request.call_id; + replaces += `;to-tag=${options.replaces._to_tag}`; + replaces += `;from-tag=${options.replaces._from_tag}`; + } replaces = encodeURIComponent(replaces); } diff --git a/src/test/test-ReferSubscriber.ts b/src/test/test-ReferSubscriber.ts new file mode 100644 index 00000000..451e8deb --- /dev/null +++ b/src/test/test-ReferSubscriber.ts @@ -0,0 +1,71 @@ +import './include/common'; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const ReferSubscriber = require('../RTCSession/ReferSubscriber.js'); + +const TARGET = 'sip:bob@example.com'; +const CALL_ID = 'a84b4c76e66710'; +const LOCAL_TAG = 'localtag'; +const REMOTE_TAG = 'remotetag'; + +// Session to replace, with the fields set by RTCSession once its dialog is +// confirmed. +function createReplacedSession(direction: 'outgoing' | 'incoming'): object { + const outgoing = direction === 'outgoing'; + + return { + _request: { call_id: CALL_ID }, + // Tags of the From and To header fields of the initial INVITE. + _from_tag: outgoing ? LOCAL_TAG : REMOTE_TAG, + _to_tag: outgoing ? REMOTE_TAG : LOCAL_TAG, + _dialog: { + id: { call_id: CALL_ID, local_tag: LOCAL_TAG, remote_tag: REMOTE_TAG }, + }, + }; +} + +// Sends a REFER that replaces the given session and returns its Refer-To +// header field. +function sendRefer(replaces: object): string | undefined { + let extraHeaders: string[] = []; + + const session = { + contact: '', + _ua: { + _configuration: { + uri: { scheme: 'sip', user: 'alice', host: 'example.com' }, + }, + }, + sendRequest(_method: string, options: { extraHeaders: string[] }) { + extraHeaders = options.extraHeaders; + + return { cseq: 1 }; + }, + }; + + new ReferSubscriber(session).sendRefer(TARGET, { replaces }); + + return extraHeaders.find(header => header.startsWith('Refer-To:')); +} + +describe('ReferSubscriber', () => { + test('Replaces of an outgoing session', () => { + expect(sendRefer(createReplacedSession('outgoing'))).toBe( + 'Refer-To: ' + ); + }); + + test('Replaces of an incoming session', () => { + expect(sendRefer(createReplacedSession('incoming'))).toBe( + 'Refer-To: ' + ); + }); + + test('Replaces of a session without dialog', () => { + const replaces = { ...createReplacedSession('outgoing'), _dialog: null }; + + expect(sendRefer(replaces)).toBe( + 'Refer-To: ' + ); + }); +});