@@ -38,6 +38,12 @@ RTCSession = function(ua) {
3838 this . earlyDialogs = { } ;
3939 this . rtcMediaHandler = null ;
4040
41+ // RTCSession confirmation flag
42+ this . is_confirmed = false ;
43+
44+ // is late SDP being negotiated
45+ this . late_sdp = false ;
46+
4147 // Session Timers
4248 this . timers = {
4349 ackTimer : null ,
@@ -286,11 +292,19 @@ RTCSession.prototype.answer = function(options) {
286292 return ;
287293 }
288294
289- self . rtcMediaHandler . createAnswer (
290- answerCreationSucceeded ,
291- answerCreationFailed ,
292- RTCAnswerConstraints
293- ) ;
295+ if ( self . late_sdp ) {
296+ self . rtcMediaHandler . createOffer (
297+ sdpCreationSucceeded ,
298+ sdpCreationFailed ,
299+ RTCAnswerConstraints
300+ ) ;
301+ } else {
302+ self . rtcMediaHandler . createAnswer (
303+ sdpCreationSucceeded ,
304+ sdpCreationFailed ,
305+ RTCAnswerConstraints
306+ ) ;
307+ }
294308 } ,
295309
296310 // rtcMediaHandler.addStream failed
@@ -302,8 +316,8 @@ RTCSession.prototype.answer = function(options) {
302316 self . failed ( 'system' , null , JsSIP . C . causes . WEBRTC_ERROR ) ;
303317 } ,
304318
305- // rtcMediaHandler.createAnswer succeeded
306- answerCreationSucceeded = function ( body ) {
319+ // rtcMediaHandler.createAnswer or rtcMediaHandler.createOffer succeeded
320+ sdpCreationSucceeded = function ( body ) {
307321 var
308322 // run for reply success callback
309323 replySucceeded = function ( ) {
@@ -326,8 +340,8 @@ RTCSession.prototype.answer = function(options) {
326340 ) ;
327341 } ,
328342
329- // rtcMediaHandler.createAnsewr failed
330- answerCreationFailed = function ( ) {
343+ // rtcMediaHandler.createAnswer or rtcMediaHandler.createOffer failed
344+ sdpCreationFailed = function ( ) {
331345 if ( self . status === C . STATUS_TERMINATED ) {
332346 return ;
333347 }
@@ -804,10 +818,45 @@ RTCSession.prototype.getRemoteStreams = function() {
804818RTCSession . prototype . init_incoming = function ( request ) {
805819 var expires ,
806820 self = this ,
807- contentType = request . getHeader ( 'Content-Type' ) ;
821+ contentType = request . getHeader ( 'Content-Type' ) ,
822+
823+ waitForAnswer = function ( ) {
824+ self . status = C . STATUS_WAITING_FOR_ANSWER ;
825+
826+ // Set userNoAnswerTimer
827+ self . timers . userNoAnswerTimer = window . setTimeout ( function ( ) {
828+ request . reply ( 408 ) ;
829+ self . failed ( 'local' , null , JsSIP . C . causes . NO_ANSWER ) ;
830+ } , self . ua . configuration . no_answer_timeout
831+ ) ;
832+
833+ /* Set expiresTimer
834+ * RFC3261 13.3.1
835+ */
836+ if ( expires ) {
837+ self . timers . expiresTimer = window . setTimeout ( function ( ) {
838+ if ( self . status === C . STATUS_WAITING_FOR_ANSWER ) {
839+ request . reply ( 487 ) ;
840+ self . failed ( 'system' , null , JsSIP . C . causes . EXPIRES ) ;
841+ }
842+ } , expires
843+ ) ;
844+ }
845+
846+ // Fire 'newRTCSession' event.
847+ self . newRTCSession ( 'remote' , request ) ;
848+
849+ // Reply 180.
850+ request . reply ( 180 , null , [ 'Contact: ' + self . contact ] ) ;
851+
852+ // Fire 'progress' event.
853+ // TODO: Document that 'response' field in 'progress' event is null for
854+ // incoming calls.
855+ self . progress ( 'local' , null ) ;
856+ } ;
808857
809858 // Check body and content type
810- if ( ! request . body || ( contentType !== 'application/sdp' ) ) {
859+ if ( request . body && ( contentType !== 'application/sdp' ) ) {
811860 request . reply ( 415 ) ;
812861 return ;
813862 }
@@ -845,57 +894,29 @@ RTCSession.prototype.init_incoming = function(request) {
845894 constraints : { "optional" : [ { 'DtlsSrtpKeyAgreement' : 'true' } ] }
846895 } ) ;
847896
848- this . rtcMediaHandler . onMessage (
849- 'offer' ,
850- request . body ,
851- /*
852- * onSuccess
853- * SDP Offer is valid. Fire UA newRTCSession
854- */
855- function ( ) {
856- self . status = C . STATUS_WAITING_FOR_ANSWER ;
857-
858- // Set userNoAnswerTimer
859- self . timers . userNoAnswerTimer = window . setTimeout ( function ( ) {
860- request . reply ( 408 ) ;
861- self . failed ( 'local' , null , JsSIP . C . causes . NO_ANSWER ) ;
862- } , self . ua . configuration . no_answer_timeout
863- ) ;
864-
865- /* Set expiresTimer
866- * RFC3261 13.3.1
897+ if ( request . body ) {
898+ this . rtcMediaHandler . onMessage (
899+ 'offer' ,
900+ request . body ,
901+ /*
902+ * onSuccess
903+ * SDP Offer is valid. Fire UA newRTCSession
867904 */
868- if ( expires ) {
869- self . timers . expiresTimer = window . setTimeout ( function ( ) {
870- if ( self . status === C . STATUS_WAITING_FOR_ANSWER ) {
871- request . reply ( 487 ) ;
872- self . failed ( 'system' , null , JsSIP . C . causes . EXPIRES ) ;
873- }
874- } , expires
875- ) ;
905+ waitForAnswer ,
906+ /*
907+ * onFailure
908+ * Bad media description
909+ */
910+ function ( e ) {
911+ self . logger . warn ( 'invalid SDP' ) ;
912+ self . logger . warn ( e ) ;
913+ request . reply ( 488 ) ;
876914 }
877-
878- // Fire 'newRTCSession' event.
879- self . newRTCSession ( 'remote' , request ) ;
880-
881- // Reply 180.
882- request . reply ( 180 , null , [ 'Contact: ' + self . contact ] ) ;
883-
884- // Fire 'progress' event.
885- // TODO: Document that 'response' field in 'progress' event is null for
886- // incoming calls.
887- self . progress ( 'local' , null ) ;
888- } ,
889- /*
890- * onFailure
891- * Bad media description
892- */
893- function ( e ) {
894- self . logger . warn ( 'invalid SDP' ) ;
895- self . logger . warn ( e ) ;
896- request . reply ( 488 ) ;
897- }
898- ) ;
915+ ) ;
916+ } else {
917+ this . late_sdp = true ;
918+ waitForAnswer ( ) ;
919+ }
899920} ;
900921
901922RTCSession . prototype . connect = function ( target , options ) {
@@ -1106,39 +1127,18 @@ RTCSession.prototype.receiveReinvite = function(request) {
11061127 sdp , idx , direction ,
11071128 self = this ,
11081129 contentType = request . getHeader ( 'Content-Type' ) ,
1109- hold = true ;
1110-
1111- if ( ! request . body ) {
1112- this . logger . warn ( 're-INVITE without body not implemented' ) ;
1113- request . reply ( 415 , 're-INVITE Without Body Not Implemented' ) ;
1114- return ;
1115- }
1130+ hold = false ,
11161131
1117- if ( contentType !== 'application/sdp' ) {
1118- this . logger . warn ( 'invalid Content-Type' ) ;
1119- request . reply ( 415 ) ;
1120- return ;
1121- }
1122-
1123- sdp = JsSIP . Parser . parseSDP ( request . body ) ;
1124-
1125- for ( idx = 0 ; idx < sdp . media . length ; idx ++ ) {
1126- direction = sdp . direction || sdp . media [ idx ] . direction || 'sendrecv' ;
1127-
1128- if ( direction !== 'sendonly' && direction !== 'inactive' ) {
1129- hold = false ;
1130- }
1131- }
1132+ createSdp = function ( onSuccess , onFailure ) {
1133+ if ( self . late_sdp ) {
1134+ self . rtcMediaHandler . createOffer ( onSuccess , onFailure ) ;
1135+ } else {
1136+ self . rtcMediaHandler . createAnswer ( onSuccess , onFailure ) ;
1137+ }
1138+ } ,
11321139
1133- this . rtcMediaHandler . onMessage (
1134- 'offer' ,
1135- request . body ,
1136- /*
1137- * onSuccess
1138- * SDP Offer is valid
1139- */
1140- function ( ) {
1141- self . rtcMediaHandler . createAnswer (
1140+ answer = function ( ) {
1141+ createSdp (
11421142 // onSuccess
11431143 function ( body ) {
11441144 request . reply ( 200 , null , [ 'Contact: ' + self . contact ] , body ,
@@ -1160,16 +1160,47 @@ RTCSession.prototype.receiveReinvite = function(request) {
11601160 request . reply ( 500 ) ;
11611161 }
11621162 ) ;
1163- } ,
1164- /*
1165- * onFailure
1166- * Bad media description
1167- */
1168- function ( e ) {
1169- self . logger . error ( e ) ;
1170- request . reply ( 488 ) ;
1163+ } ;
1164+
1165+
1166+ if ( request . body ) {
1167+ if ( contentType !== 'application/sdp' ) {
1168+ this . logger . warn ( 'invalid Content-Type' ) ;
1169+ request . reply ( 415 ) ;
1170+ return ;
11711171 }
1172- ) ;
1172+
1173+ sdp = JsSIP . Parser . parseSDP ( request . body ) ;
1174+
1175+ for ( idx = 0 ; idx < sdp . media . length ; idx ++ ) {
1176+ direction = sdp . direction || sdp . media [ idx ] . direction || 'sendrecv' ;
1177+
1178+ if ( direction === 'sendonly' || direction === 'inactive' ) {
1179+ hold = true ;
1180+ }
1181+ }
1182+
1183+ this . rtcMediaHandler . onMessage (
1184+ 'offer' ,
1185+ request . body ,
1186+ /*
1187+ * onSuccess
1188+ * SDP Offer is valid
1189+ */
1190+ answer ,
1191+ /*
1192+ * onFailure
1193+ * Bad media description
1194+ */
1195+ function ( e ) {
1196+ self . logger . error ( e ) ;
1197+ request . reply ( 488 ) ;
1198+ }
1199+ ) ;
1200+ } else {
1201+ this . late_sdp = true ;
1202+ answer ( ) ;
1203+ }
11731204} ;
11741205
11751206/**
@@ -1244,7 +1275,8 @@ RTCSession.prototype.receiveUpdate = function(request) {
12441275 * In dialog Request Reception
12451276 */
12461277RTCSession . prototype . receiveRequest = function ( request ) {
1247- var contentType ;
1278+ var contentType ,
1279+ self = this ;
12481280
12491281 if ( request . method === JsSIP . C . CANCEL ) {
12501282 /* RFC3261 15 States that a UAS may have accepted an invitation while a CANCEL
@@ -1270,8 +1302,40 @@ RTCSession.prototype.receiveRequest = function(request) {
12701302 if ( this . status === C . STATUS_WAITING_FOR_ACK ) {
12711303 window . clearTimeout ( this . timers . ackTimer ) ;
12721304 window . clearTimeout ( this . timers . invite2xxTimer ) ;
1273- this . status = C . STATUS_CONFIRMED ;
1274- this . confirmed ( 'remote' , request ) ;
1305+
1306+ if ( this . late_sdp ) {
1307+ if ( ! request . body ) {
1308+ self . ended ( 'remote' , request , JsSIP . C . causes . MISSING_SDP ) ;
1309+ break ;
1310+ }
1311+
1312+ this . rtcMediaHandler . onMessage (
1313+ 'answer' ,
1314+ request . body ,
1315+ /*
1316+ * onSuccess
1317+ * SDP Answer fits with Offer. Media will start
1318+ */
1319+ function ( ) {
1320+ self . late_sdp = false ;
1321+ self . status = C . STATUS_CONFIRMED ;
1322+ } ,
1323+ /*
1324+ * onFailure
1325+ * SDP Answer does not fit the Offer. Accept the call and Terminate.
1326+ */
1327+ function ( e ) {
1328+ self . logger . warn ( e ) ;
1329+ self . ended ( 'remote' , request , JsSIP . C . causes . BAD_MEDIA_DESCRIPTION ) ;
1330+ }
1331+ ) ;
1332+ } else {
1333+ this . status = C . STATUS_CONFIRMED ;
1334+ }
1335+
1336+ if ( this . status === C . STATUS_CONFIRMED && ! this . is_confirmed ) {
1337+ this . confirmed ( 'remote' , request ) ;
1338+ }
12751339 }
12761340 break ;
12771341 case JsSIP . C . BYE :
@@ -1579,7 +1643,7 @@ RTCSession.prototype.receiveInviteResponse = function(response) {
15791643 this . status = C . STATUS_CONFIRMED ;
15801644
15811645 if ( ! response . body ) {
1582- this . acceptAndTerminate ( response , 400 , 'Missing session description' ) ;
1646+ this . acceptAndTerminate ( response , 400 , JsSIP . C . causes . MISSING_SDP ) ;
15831647 this . failed ( 'remote' , response , JsSIP . C . causes . BAD_MEDIA_DESCRIPTION ) ;
15841648 break ;
15851649 }
@@ -1597,10 +1661,8 @@ RTCSession.prototype.receiveInviteResponse = function(response) {
15971661 * SDP Answer fits with Offer. Media will start
15981662 */
15991663 function ( ) {
1600- var ack ;
1601-
16021664 session . accepted ( 'remote' , response ) ;
1603- ack = session . sendRequest ( JsSIP . C . ACK ) ;
1665+ session . sendRequest ( JsSIP . C . ACK ) ;
16041666 session . confirmed ( 'local' , null ) ;
16051667 } ,
16061668 /*
@@ -1812,6 +1874,8 @@ RTCSession.prototype.confirmed = function(originator, ack) {
18121874 var session = this ,
18131875 event_name = 'confirmed' ;
18141876
1877+ this . is_confirmed = true ;
1878+
18151879 session . emit ( event_name , session , {
18161880 originator : originator ,
18171881 ack : ack || null
0 commit comments