@@ -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