1 /*
  2 * Copyright (C) 2012 Doubango Telecom <http://www.doubango.org>
  3 * License: BSD
  4 * This file is part of Open Source sipML5 solution <http://www.sipml5.org>
  5 */
  6 
  7 /**
  8 @fileoverview This is SIPML5 "library" contains a  lot of classes and functions.
  9 
 10 @name sipML5 API
 11 @author      Doubango Telecom <http://www.doubango.org>
 12 @version     1.3.203
 13 */
 14 
 15 /** 
 16 @namespace
 17 @description Root namesapce.
 18 */
 19 SIPml = {};
 20 
 21 /** @private */SIPml.b_initialized = false;
 22 /** @private */SIPml.b_initializing = false;
 23 /** @private */SIPml.s_navigator_friendly_name = 'unknown';
 24 /** @private */SIPml.b_navigator_outdated = false;
 25 /** @private */SIPml.s_navigator_version = 'unknown';
 26 /** @private */SIPml.s_system_friendly_name = 'unknown';
 27 /** @private */SIPml.b_webrtc4all_plugin_outdated = false;
 28 /** @private */SIPml.b_webrtc4all_supported = false;
 29 /** @private */SIPml.s_webrtc4all_version = 'unknown';
 30 /** @private */SIPml.b_have_media_stream = false;
 31 /** @private */SIPml.b_webrtc_supported = false;
 32 
 33 
 34 /**
 35 Sets the debug level.
 36 @since version 1.3.203
 37 @param {String} level The level. Supported values: <i>info</i>, <i>warn</i>, <i>error</i> and <i>fatal</i>.
 38 */
 39 SIPml.setDebugLevel = function(level) {
 40     tsk_utils_log_set_level(level === 'fatal' ? 1 : (level === 'error' ? 2 : (level === 'warn' ? 3 : 4)));
 41 }
 42 
 43 /**
 44 Gets the version name of the installed <a href="http://code.google.com/p/webrtc4all/">webrtc4all plugin</a>.
 45 You must <a href="#.init">initialize</a> the engine before calling this function.
 46 @static
 47 @returns {String} Version name (e.g. '1.12.756')
 48 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
 49 */
 50 SIPml.getWebRtc4AllVersion = function() {
 51     if(!SIPml.isInitialized()){
 52         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
 53     }
 54     return SIPml.s_webrtc4all_version;
 55 };
 56 
 57 /**
 58 Gets the web browser version (e.g. <i>'1.5.beta'</i>).
 59 You must <a href="#.init">initialize</a> the engine before calling this function.
 60 @static
 61 @returns {String} The the web browser version.
 62 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
 63 */
 64 SIPml.getNavigatorVersion = function() {
 65     if(!SIPml.isInitialized()){
 66         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
 67     }
 68     return SIPml.s_navigator_version; 
 69 };
 70 
 71 /**
 72 Gets the web browser friendly name (e.g. <i>'chrome'</i>, <i>'firefox'</i>, <i>'safari'</i>, <i>'opera'</i>, <i>'ie'</i> or <i>'netscape'</i>).
 73 You must <a href="#.init">initialize</a> the engine before calling this function.
 74 @static
 75 @returns {String} The web browser friendly name.
 76 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
 77 */
 78 SIPml.getNavigatorFriendlyName = function() {
 79     if(!SIPml.isInitialized()){
 80         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
 81     }
 82     return SIPml.s_navigator_friendly_name; 
 83 };
 84 
 85 /**
 86 Gets the Operating System friendly name (e.g. <i>'windows'</i>, <i>'mac'</i>, <i>'lunix'</i>, <i>'solaris'</i>, <i>'sunos'</i> or <i>'powerpc'</i>).
 87 You must <a href="#.init">initialize</a> the engine before calling this function.
 88 @static
 89 @returns {String} The Operating System friendly name.
 90 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
 91 */
 92 SIPml.getSystemFriendlyName = function() {
 93     if(!SIPml.isInitialized()){
 94         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
 95     }
 96     return SIPml.s_system_friendly_name; 
 97 };
 98 
 99 /**
100 Checks whether the web browser supports WebRTC but is outdated.
101 You must <a href="#.init">initialize</a> the engine before calling this function.
102 @static
103 @returns {Boolean} <i>true</i> if outdated; otherwise <i>false</i>
104 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
105 */
106 SIPml.isNavigatorOutdated= function () {
107     if(!SIPml.isInitialized()){
108         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
109     }
110     return SIPml.b_navigator_outdated; 
111 }
112 
113 /**
114 Checks whether the <a href="http://code.google.com/p/webrtc4all/">webrtc4all plugin</a> is outdated or not.
115 You must <a href="#.init">initialize</a> the engine before calling this function.
116 @static
117 @returns {Boolean} <i>true</i> if outdated; otherwise <i>false</i>
118 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
119 */
120 SIPml.isWebRtc4AllPluginOutdated = function(){
121     if(!SIPml.isInitialized()){
122         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
123     }
124     return SIPml.b_webrtc4all_plugin_outdated; 
125 }
126 
127 /**
128 Checks whether the <a href="http://code.google.com/p/webrtc4all/">webrtc4all plugin</a> is installed or not.
129 You must <a href="#.init">initialize</a> the engine before calling this function.
130 @static
131 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i>
132 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
133 */
134 SIPml.isWebRtc4AllSupported = function(){
135     if(!SIPml.isInitialized()){
136         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
137     }
138     return SIPml.b_webrtc4all_supported; 
139 }
140 
141 /**
142 Checks whether Screen share is supported on this browser.
143 You must <a href="#.init">initialize</a> the engine before calling this function.
144 @since version 1.3.203
145 @static
146 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i>
147 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
148 */
149 SIPml.isScreenShareSupported = function () {
150     if(!SIPml.isInitialized()){
151         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
152     }
153     return (navigator.userAgent.match('Chrome') && parseInt(navigator.userAgent.match(/Chrome\/(.*) /)[1]) >= 26);
154 }
155 
156 /**
157 Checks whether WebRTC is supported or not.
158 You must <a href="#.init">initialize</a> the engine before calling this function.
159 @static
160 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i>
161 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
162 */
163 SIPml.isWebRtcSupported = function () {
164     if(!SIPml.isInitialized()){
165         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
166     }
167     return SIPml.b_webrtc_supported; 
168 }
169 
170 /**
171 Checks whether WebSocket is supported or not.
172 @static
173 @returns {Boolean} <i>true</i> if supported; otherwise <i>false</i>
174 */
175 SIPml.isWebSocketSupported = function () {
176     return tsk_utils_have_websocket(); 
177 }
178 
179 /**
180 Checks whether <a href="https://developer.mozilla.org/en-US/docs/WebRTC/navigator.getUserMedia">getUserMedia</a> is supported or not. The engined must be initialized before calling this function.
181 @static
182 @returns {Boolean} <i>true</i> if <a href="https://developer.mozilla.org/en-US/docs/WebRTC/navigator.getUserMedia">getUserMedia</a> is supported; otherwise <i>false</i>
183 */
184 SIPml.haveMediaStream = function () {
185     if(!SIPml.isInitialized()){
186         throw new Error("ERR_NOT_INITIALIZED: Engine not initialized yet. Please call 'SIPml.init()' first");
187     }
188     return SIPml.b_have_media_stream;
189 }
190 
191 /**
192 Checks whether the engine is ready to make/receive calls or not. <br />
193 The engine is ready when:
194     <ul>
195         <li>engine is <a href="#.init">initialized</a></li>
196         <li>webrtc is supported</li>
197         <li>we got a valid media stream (from <a href="https://developer.mozilla.org/en-US/docs/WebRTC/navigator.getUserMedia">getUserMedia</a>)</li>
198     </ul>
199 @static
200 @returns {Boolean} <i>true</i> if the engine is ready; otherwise <i>false</i>
201 @throws {ERR_NOT_INITIALIZED} <font color="red">ERR_NOT_INITIALIZED</font> if the engine is not <a href="#.init">initialized</a>.
202 */
203 SIPml.isReady = function () {
204     return (SIPml.isInitialized() && SIPml.isWebRtcSupported() && SIPml.haveMediaStream()); 
205 }
206 
207 /**
208 Checks whether the engine is initialized or not. To initialize the stack you must call <a href="#.init">init()</a> function.
209 @static
210 @returns {Boolean} <i>true</i> if the engine is initialized; otherwise <i>false</i>
211 */
212 SIPml.isInitialized = function () { return SIPml.b_initialized; }
213 
214 
215 /**
216 Initialize the engine. <b>You must call this function before any other.</b>.
217 @param {CallbackFunction} [readyCallback] Optional callback function to call when the stack finish initializing and become ready.
218 @param {CallbackFunction} [errorCallback] Optional callback function to call when initialization fails.
219 
220 @example
221 SIPml.init(function(e){ console.info('engine is ready'); }, function(e){ console.info('Error: ' + e.message); });
222 @static
223 */
224 SIPml.init = function (successCallback, errorCallback) {
225     if (!SIPml.b_initialized && !SIPml.b_initializing) {
226         SIPml.b_initializing = true;
227         tsk_utils_init_webrtc();
228 
229         tsk_utils_log_info('User-Agent=' + (navigator.userAgent || "unknown"));
230 
231         SIPml.b_have_media_stream = tsk_utils_have_stream();
232         SIPml.b_webrtc_supported = tsk_utils_have_webrtc();
233         SIPml.b_webrtc4all_supported = tsk_utils_have_webrtc4all();
234         SIPml.s_webrtc4all_version = tsk_utils_webrtc4all_get_version();
235         SIPml.s_navigator_friendly_name = tsk_utils_get_navigator_friendly_name();
236         SIPml.s_system_friendly_name = tsk_utils_get_system_friendly_name();
237 
238         // prints whether WebSocket is supported
239         tsk_utils_log_info("WebSocket supported = " + (SIPml.isWebSocketSupported() ? "yes" : "no"));
240 
241         // check webrtc4all version
242         if (tsk_utils_have_webrtc4all()) {
243             tsk_utils_log_info("WebRTC type = " + WebRtc4all_GetType() + " version = " + tsk_utils_webrtc4all_get_version());
244             if (SIPml.s_webrtc4all_version != '1.35.981') {
245                 SIPml.b_webrtc4all_plugin_outdated = true;
246             }
247         }
248 
249         // prints navigator friendly name
250         tsk_utils_log_info("Navigator friendly name = " + SIPml.s_navigator_friendly_name);
251 
252         // gets navigator version
253         if(SIPml.s_navigator_friendly_name == 'ie'){
254             var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
255             if (re.exec(navigator.userAgent) != null) {
256                 SIPml.s_navigator_version = RegExp.$1;
257             }
258         }        
259 
260         // prints OS friendly name
261         tsk_utils_log_info("OS friendly name = " + SIPml.s_system_friendly_name);
262         // prints support for WebRTC (native or plugin)
263         tsk_utils_log_info("Have WebRTC = " + (tsk_utils_have_webrtc() ? "yes" : "false"));
264         // prints support for getUserMedia
265         tsk_utils_log_info("Have GUM = " + (tsk_utils_have_stream() ? "yes" : "false"));
266 
267         // checks for WebRTC support
268         if (!tsk_utils_have_webrtc()) {
269             // is it chrome?
270             if (SIPml.s_navigator_friendly_name == "chrome") {
271                 SIPml.b_navigator_outdated = true;
272                 return;
273             }
274 
275             // for now the plugins (WebRTC4all only works on Windows)
276             if (SIPml.s_system_friendly_name == 'win' || SIPml.s_system_friendly_name == 'windows') {
277                 // Internet explorer
278                 if (SIPml.s_navigator_friendly_name == 'ie') {
279                     // Check for IE version 
280                     var rv = -1;
281                     var ua = navigator.userAgent;
282                     var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
283                     if (re.exec(ua) != null) {
284                         rv = parseFloat(RegExp.$1);
285                     }
286                     if (rv < 9.0) {
287                         SIPml.b_navigator_outdated = true;
288                         return;
289                     }
290 
291                     // break page loading ('window.location' won't stop JS execution)
292                     if (!tsk_utils_have_webrtc4all()) {
293                         return;
294                     }
295                 }
296             }
297         }
298 
299         if(SIPml.b_webrtc_supported && SIPml.b_have_media_stream){
300             SIPml.b_initialized = true;
301             SIPml.b_initializing = false;
302             tsk_utils_log_info("Engine initialized");
303             if(successCallback){
304                 successCallback({});
305             }
306         }
307         else{
308             if(errorCallback){
309                 var s_description = !SIPml.b_webrtc_supported ? "WebRTC not supported" : (!SIPml.b_have_media_stream ? "getUserMedia not supported" : "Internal error");
310                 errorCallback({description: s_description});
311             }
312         }
313     }
314 }
315 
316 // ================================== SIPml.EventTarget ==========================================
317 
318 /**
319 @constructor
320 Defines an event target. You sould never create an event target object.
321 */
322 SIPml.EventTarget = function () {
323     this.ao_listeners = [];
324 }
325 
326 /**
327 Adds an event listener to the target object. <br /><br />
328 <table border="1">
329     <tr>
330         <td><b>Target classes<b></td>
331         <td><b>Supported event types<b></td>
332         <td><b>Raised event object<b></td>
333         <td><b>Remarques<b></td>
334     </tr>
335     <tr>
336         <td><a href="SIPml.Stack.html" name="SIPml.EventTarget.Stack">SIPml.Stack</a></td>
337         <td>
338             <b>*</b><br/> starting<br/> started<br/> stopping<br/> stopped<br/> failed_to_start<br/> failed_to_stop<br/> i_new_call<br /> i_new_message<br />
339             m_permission_requested<br/> m_permission_accepted<br/> m_permission_refused
340         </td>
341         <td><a href="SIPml.Stack.Event.html">SIPml.Stack.Event</a></td>
342         <td>'*' is used to listen for all events</td>
343     </tr>
344     <tr>
345         <td>
346             <a href="SIPml.Session.html" name="SIPml.EventTarget.Session">SIPml.Session</a>
347             <ul>
348                 <li><a href="SIPml.Session.Call.html">SIPml.Session.Call</a></li>
349                 <li><a href="SIPml.Session.Message.html">SIPml.Session.Message</a></li>
350                 <li><a href="SIPml.Session.Message.html">SIPml.Session.Registration</a></li>
351                 <li><a href="SIPml.Session.Message.html">SIPml.Session.Subscribe</a></li>
352                 <li><a href="SIPml.Session.Message.html">SIPml.Session.Publish</a></li>
353             <ul>
354         </td>
355         <td><b>*</b><br/> connecting<br/> connected<br/> terminating<br/> terminated<br/>
356                 i_ao_request<br />
357                 media_added<br/> media_removed<br/>
358                 i_request<br/> o_request<br/> cancelled_request<br/> sent_request<br/>
359                 transport_error<br/> global_error<br/> message_error<br/> webrtc_error
360         </td>
361         <td><a href="SIPml.Session.Event.html">SIPml.Session.Event</a></td>
362         <td>'*' is used to listen for all events<br /></td>
363     </tr>
364     <tr>
365         <td><a href="SIPml.Session.Call.html" name="SIPml.EventTarget.Session.Call">SIPml.Session.Call</a></td>
366         <td>
367             m_early_media<br/> m_local_hold_ok<br/> m_local_hold_nok<br/> m_local_resume_ok<br/> m_local_resume_nok<br/> m_remote_hold<br/> m_remote_resume<br/>
368             m_stream_video_local_added<br /> m_stream_video_local_removed<br/> m_stream_video_remote_added<br/> m_stream_video_remote_removed <br />
369             m_stream_audio_local_added<br /> m_stream_audio_local_removed<br/> m_stream_audio_remote_added<br/> m_stream_audio_remote_removed <br />
370             i_ect_new_call<br/> o_ect_trying<br/> o_ect_accepted<br/> o_ect_completed<br/> i_ect_completed<br/> o_ect_failed<br/> i_ect_failed<br/> o_ect_notify<br/> i_ect_notify<br/> i_ect_requested
371         </td>
372         <td><a href="SIPml.Session.Event.html">SIPml.Session.Event</a></td>
373         <td>borrows all events supported by <a href="SIPml.Session.html">SIPml.Session</a></td>
374     </tr>
375     <tr>
376         <td><a href="SIPml.Session.Subscribe.html" name="SIPml.EventTarget.Session.Subscribe">SIPml.Session.Subscribe</a></td>
377         <td>
378             i_notify
379         </td>
380         <td><a href="SIPml.Session.Event.html">SIPml.Session.Event</a></td>
381         <td>borrows all events supported by <a href="SIPml.Session.html">SIPml.Session</a></td>
382     </tr>
383 </table>
384 @param {String|Array} type The event type/identifier. Must not be null or empty. Use <b>'*'</b> to listen for all events.
385 @param {function} listener The object that receives a notification when an event of the specified type occurs. This must be an object implementing the <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventListener">EventListener</a> interface, or simply a JavaScript function.
386 @example
387 // listen for a single event
388 this.addEventListener('started', function(e){
389     console.info("'started' event fired");
390 });
391 // or listen for two or more events
392 this.addEventListener(['started', 'stopped'], function(e){
393     console.info("'"+e.type+"' event fired");
394 });
395 // or listen for all events
396 this.addEventListener('*', function(e){
397     console.info("'"+e.type+"' event fired");
398 });
399 @see <a href="#removeEventListener">removeEventListener</a>
400 @throws {ERR_INVALID_PARAMETER_VALUE|ERR_INVALID_PARAMETER_TYPE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_INVALID_PARAMETER_TYPE</font>
401 */
402 SIPml.EventTarget.prototype.addEventListener = function (o_type, o_listener) {
403     if (!o_listener) {
404         throw new Error("ERR_INVALID_PARAMETER_VALUE: 'listener' must not be null");
405     }
406     if (!o_type) {
407         throw new Error("ERR_INVALID_PARAMETER_VALUE: 'type' must not be null");
408     }
409     if(!(o_type instanceof String || typeof o_type == "string" || o_type instanceof Array)){
410         throw new Error("ERR_INVALID_PARAMETER_TYPE: 'type' must be a string or array");
411     }
412     
413     if(o_type instanceof Array){
414         var This = this;
415         o_type.forEach(function (s_type) {
416             if (!tsk_string_is_null_or_empty(s_type) && tsk_string_is_string(s_type)) {
417                 This.ao_listeners[s_type] = o_listener;
418             }
419         });
420     }
421     else{
422         this.ao_listeners[o_type] = o_listener;
423     }
424 }
425 
426 /**
427 Removes an event listener from the target object.
428 @param {String} type The event type/identifier to stop listening for.
429 @see <a href="#addEventListener">addEventListener</a>
430 @exemple
431 this.removeEventListener('started');
432 */
433 SIPml.EventTarget.prototype.removeEventListener = function (s_type) {
434     if (tsk_string_is_string(s_type) && !tsk_string_is_null_or_empty(s_type)) {
435         this.ao_listeners[s_type] = undefined;
436     }
437 }
438 
439 /**
440 @ignore
441 @private
442 @param {Object} event
443 */
444 SIPml.EventTarget.prototype.dispatchEvent = function (o_event) {
445     var o_listener = (this.ao_listeners[o_event.s_type] || this.ao_listeners['*']);
446     if (o_listener) {
447         o_listener.call(this, o_event.o_value);
448     }
449 }
450 
451 
452 
453 // ================================== SIPml.Event ==========================================
454 
455 
456 /** 
457 SIP  event object. You should never create an instance of this class by yourself.
458 @constructor
459 @param {String} type The event type or identifier. Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Session">this link</a> for more information about all supported session event types.
460 @param {tsip_event} [event] Private wrapped session object.
461 @property {String} type The event <a href="SIPml.EventTarget.html#SIPml.EventTarget.Session">type or identifier</a> (e.g. <i>'connected'</i>).
462 @property {String} description User-friendly description in english (e.g. <i>'Session is now connected'</i>).
463 */
464 SIPml.Event = function (s_type, o_event) {
465     this.type = s_type;
466     this.description = o_event ? o_event.s_phrase : s_type;
467     this.o_event = o_event;
468 }
469 
470 /**
471 Gets the SIP response code.
472 @returns {Integer} The SIP response code (e.g. 404).
473 */
474 SIPml.Event.prototype.getSipResponseCode = function () {
475     var o_message = this.o_event ? this.o_event.get_message() : null;
476     if (o_message && o_message.is_response()) {
477         return o_message.get_response_code();
478     }
479     return -1;
480 }
481 
482 /**
483 Gets the SIP content associated to this event. This function could be called to get the content of the incoming SIP message ('i_new_message' event).
484 @returns {Object} SIP content.
485 @see <a href="#getContentType">getContentType</a>, <a href="#getContentString">getContentString</a>
486 */
487 SIPml.Event.prototype.getContent = function () {
488     var o_message = this.o_event ? this.o_event.get_message() : null;
489     if (o_message) {
490         return o_message.get_content();
491     }
492     return null;
493 }
494 
495 /**
496 Gets the SIP content associated to this event. This function could be called to get the content of the incoming SIP message ('i_new_message' event).
497 @returns {String} SIP content.
498 @see <a href="#getContentType">getContentType</a>, <a href="#getContent">getContent</a>
499 */
500 SIPml.Event.prototype.getContentString = function () {
501     var o_message = this.o_event ? this.o_event.get_message() : null;
502     if (o_message) {
503         return o_message.get_content_as_string();
504     }
505     return null;
506 }
507 
508 /**
509 Gets the SIP content-type associated to this event. This function could be called to get the content-type of the incoming SIP message ('i_new_message' event).
510 @returns {Object} SIP content-type.
511 @see <a href="#getContent">getContent</a>
512 */
513 SIPml.Event.prototype.getContentType = function () {
514     var o_message = this.o_event ? this.o_event.get_message() : null;
515     if (o_message) {
516         return o_message.get_content_type();
517     }
518     return null;
519 }
520 
521 
522 
523 // ================================== SIPml.Stack ==========================================
524 
525 
526 /**
527 Anonymous SIP Stack configuration object.
528 @namespace SIPml.Stack.Configuration
529 @name SIPml.Stack.Configuration
530 @property {String} realm The domain name. Required for stack <a href="SIPml.Stack.html#constructor">constructor</a> but optional when used with <a href="SIPml.Stack.html#setConfiguration">setConfiguration</a>. <br />
531 Example: <i>example.org</i>
532 @property {String} impi The authentication name. Required for stack <a href="SIPml.Stack.html#constructor">constructor</a> but optional when used with <a href="SIPml.Stack.html#setConfiguration">setConfiguration</a>.<br />
533 Example: <i>+33600000000</i> or <i>bob</i>.
534 @property {string} impu The full SIP uri address. Required for stack <a href="SIPml.Stack.html#constructor">constructor</a> but optional when used with <a href="SIPml.Stack.html#setConfiguration">setConfiguration</a>.<br />
535 Example: <i>sip:+33600000000@example.com</i> or <i>tel:+33600000000</i> or <i>sip:bob@example.com</i>
536 @property {String} [password] The password to use for SIP authentication.<br />
537 Example: <i>mysecret</i>
538 @property {String} [display_name] The display name to use in SIP requests. This is the String displayed by the called party for incoming calls. <br />
539 Example: <i>I Am Legend</i>
540 @property {String} [websocket_proxy_url] The websocket proxy url to connect to (SIP server or gateway address). If unset the stack will use sipml5.org as host and a random port. You should not set this value unless you know what you're doing.<br />
541 Example: <i>ws://sipml5.org:5060</i>
542 @property {String} [outbound_proxy_url] The outbound Proxy URL is used to set the destination IP address and Port to use for all outgoing requests regardless the <i>domain name</i> (a.k.a <i>realm</i>). <br />
543 This is a good option for developers using a SIP domain name without valid DNS A/NAPTR/SRV records. You should not set this value unless you know what you're doing. <br />
544 Example: <i>udp://192.168.0.12:5060</i>
545 @property {Array} [ice_servers] The list of the STUN/TURN servers to use. The format must be as explained at <a target=_blank href="http://www.w3.org/TR/webrtc/#rtciceserver-type">http://www.w3.org/TR/webrtc/#rtciceserver-type</a>. <br />
546 To disable TURN/STUN to speedup ICE candidates gathering you can use an empty array. e.g. <i>[]</i>. <br />
547 Example: <i>[{ url: 'stun:stun.l.google.com:19302'}, { url:'turn:user@numb.viagenie.ca', credential:'myPassword'}]</i>
548 @property {Object} [bandwidth] Defines the maximum audio and video bandwidth to use. This will change the outhoing SDP to include a "b:AS=" attribute. Use <i>0</i> to let the browser negotiates the right value using RTCP-REMB and congestion control. Same property could be used at session level to override this value.<br />
549 <i>Available since version 1.3.203</i>. <br />
550 Example: <i>{ audio:64, video:512 }</i>
551 @property {Object} [video_size] Defines the maximum and minimum video size to be used. All values are optional. The browser will try to find the best video size between <i>max</i> and <i>min</i> based on the camera capabilities. Same property could be used at session level to override this value.<br />
552 <i>Available since version 1.3.203</i>. <br />
553 Example: <i>{ minWidth:640, minHeight:480, maxWidth:1920, maxHeight:1080 }</i>
554 @property {Boolean} [enable_rtcweb_breaker] Whether to enable the <a href="http://webrtc2sip.org/#aRTCWebBreaker" target=_blank>RTCWeb Breaker</a> module to allow calling SIP-legacy networks. <br />
555 Example: <i>true</i>
556 @property {Boolean} [enable_click2call] Whether to enable the <a href="http://click2dial.org" target=_blank>Click2Call / Click2Dial</a> service.
557 <i>Available since version 1.2.181</i>. <br />
558 Example: <i>true</i>
559 @property {Boolean} [enable_early_ims] Whether to enable 3GGP Early IMS as per <a href="http://www.arib.or.jp/english/html/overview/doc/STD-T63v9_60/5_Appendix/Rel6/33/33978-660.pdf" target=_blank>TR 33.978</a>. Should be 'true' unless you're using a real IMS network. <br />
560 <i>Available since version 1.3.203</i>. <br />
561 Example: <i>true</i>
562 @property {Boolean} [enable_media_stream_cache] Whether to reuse the same media stream for all calls. If your website is <b>not using https</b> then, the browser will request access to the camera (or microphone) every time you try to make a call. Caching the media stream will avoid getting these notifications for each call. <br />
563 <i>Available since version 1.3.203</i>. <br />
564 Example: <i>true</i>
565 
566 @property {Object} [events_listener] Object to subscribe to some events.
567 Example:
568 <ul>
569     <li><i>{ events: '*', listener: function(e){} }</i> </li>
570     <li><i>{ events: 'started', listener: function(e){} }</i></li>
571     <li><i>{ events: ['started', 'stopped'], listener: function(e){} }</i></li>
572 </ul>
573 You can also use <a href="#addEventListener">addEventListener</a> to add listeners to the stack.
574 @property {Array} [sip_headers] Stack-level SIP headers to add to all outgoing requests. Each header is an object with a <i>name</i> and <i>value</i> fields. <br />
575 Example: <i>sip_headers: [{name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, {name: 'Organization', value: 'Doubango Telecom'}]</i>
576 
577 @example
578 var configuration = {
579         realm: 'example.org',
580         impi: 'bob',
581         impu: 'sip:bob@example.org',
582         password: 'mysecret', // optional
583         display_name: 'I Am Legend', // optional
584         websocket_proxy_url: 'ws://192.168.0.10:5060', // optional
585         outbound_proxy_url: 'udp://192.168.0.12:5060', // optional
586         ice_servers: [{ url: 'stun:stun.l.google.com:19302'}, { url:'turn:user@numb.viagenie.ca', credential:'myPassword'}], // optional
587         enable_rtcweb_breaker: true, // optional
588         enable_click2call: false, // optional
589         enable_early_ims: true, // optional
590         events_listener: { events: '*', listener: listenerFunc }, // optional
591         sip_headers: [ //optional
592             {name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, 
593             {name: 'Organization', value: 'Doubango Telecom'}
594         ]
595     };
596 */
597 
598 
599 /**
600 This is the root object used by any other object to make/receive calls, messages or manage presence.
601 You have to create an instance of this class before anything else.
602 @extends SIPml.EventTarget
603 @constructor
604 @class
605 @param {SIPml.Stack.Configuration} configuration Configuration object. Could be updated later using <a href="#setConfiguration">setConfiguration</a>.
606 @throws {ERR_INVALID_PARAMETER_VALUE|ERR_INVALID_PARAMETER_TYPE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_INVALID_PARAMETER_TYPE</font>
607 @example
608 var listenerFunc = function(e){
609     console.info('stack event = ' + e.type);
610     // Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events.
611 }
612 
613 var o_stack = new SIPml.Stack({
614         realm: 'example.org',
615         impi: 'bob',
616         impu: 'sip:bob@example.org',
617         password: 'mysecret', // optional
618         display_name: 'I Am Legend', // optional
619         websocket_proxy_url: 'ws://192.168.0.10:5060', // optional
620         outbound_proxy_url: 'udp://192.168.0.12:5060', // optional
621         ice_servers: [{ url: 'stun:stun.l.google.com:19302'}, { url:'turn:user@numb.viagenie.ca', credential:'myPassword'}], // optional
622         bandwidth: { audio:64, video:512 }, // optional
623         video_size: { minWidth:640, minHeight:480, maxWidth:1920, maxHeight:1080 }, // optional
624         enable_rtcweb_breaker: true, // optional
625         enable_click2call: false, // optional
626         events_listener: { events: '*', listener: listenerFunc }, //optional
627         sip_headers: [ //optional
628             {name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, 
629             {name: 'Organization', value: 'Doubango Telecom'}
630         ]
631     }
632 );
633 
634 @see <a href="#setConfiguration">setConfiguration</a>
635 */
636 SIPml.Stack = function (o_conf) {
637     SIPml.init();
638     SIPml.EventTarget.call(this);
639     /*
640     members:
641     - o_stack {tsip_stack}
642     */
643 
644     if (!o_conf) {
645         throw new Error("ERR_INVALID_PARAMETER_VALUE: null configuration value");
646     }
647     if (tsk_string_is_null_or_empty(o_conf.realm)) {
648         throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.realm + "' is not valid as realm value");
649     }
650     if (tsk_string_is_null_or_empty(o_conf.impi)) {
651         throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.impi + "' is not valid as impi value");
652     }
653     if (tsk_string_is_null_or_empty(o_conf.impu)) {
654         throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.impu + "' is not valid as impu value");
655     }
656     // check IMPU validity
657     var o_impu = tsip_uri.prototype.Parse(o_conf.impu);
658     if (!o_impu || !o_impu.s_user_name || !o_impu.s_host) {
659         throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + o_conf.impu + "' is not valid as SIP Uri");
660     }
661 
662     var i_port;
663     var s_proxy;    
664 
665     if (!SIPml.isWebSocketSupported()) {
666         // port and host will be updated using the result from DNS SRV(NAPTR(realm))
667         i_port = 5060;
668         s_proxy = o_conf.realm;
669     }
670     else {
671         // there are at least 5 servers running on the cloud.
672         // we will connect to one of them and let the balancer to choose the right one (less connected sockets)
673         // each port can accept up to 65K connections which means that the cloud can manage 325K active connections
674         // the number of port will be increased or decreased based on the current trafic
675 
676         // webrtc2sip 2.2+ (Doubango): 
677         //      WS: 10060, 11060, 12060, 13060, 14060
678         //      WSS: 10062, 11062, 12062, 13062, 14062
679         //
680 
681         i_port = (o_conf.enable_rtcweb_breaker ? 10062 : 10060) + (((new Date().getTime()) % 5) * 1000);
682         s_proxy = "ns313841.ovh.net";
683     }
684 
685     // create the stack
686     this.o_stack = new tsip_stack(o_conf.realm, o_conf.impi, o_conf.impu, s_proxy, i_port);
687     this.o_stack.oStack = this;
688     // set configurations
689     this.setConfiguration(o_conf);
690 
691     // listen for stack events
692     this.o_stack.on_event_stack = function(e) {
693         var s_type;
694         switch (e.i_code) {
695             case tsip_event_code_e.STACK_STARTING: s_type = 'starting'; break;
696             case tsip_event_code_e.STACK_STARTED: s_type = 'started'; break;
697             case tsip_event_code_e.STACK_STOPPING: s_type = 'stopping'; break;
698             case tsip_event_code_e.STACK_STOPPED: s_type = 'stopped'; break;
699             case tsip_event_code_e.STACK_FAILED_TO_START: s_type = 'failed_to_start'; break;
700             case tsip_event_code_e.STACK_FAILED_TO_STOP: s_type = 'failed_to_stop'; break;
701         }
702         if(s_type){
703              e.o_stack.oStack.dispatchEvent({ s_type: s_type, o_value: new SIPml.Stack.Event(s_type, e) });
704         }
705     }
706 
707 
708      // listen for dialog events
709      this.o_stack.on_event_dialog = function (e) {
710          var s_type = null;
711          var i_session_id = e.o_session.i_id;
712          var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id];
713          if (!oSession) {
714              tsk_utils_log_warn('Cannot find session with id = ' + i_session_id);
715              return;
716          }
717 
718          switch (e.i_code) {
719              case tsip_event_code_e.DIALOG_TRANSPORT_ERROR: s_type = 'transport_error'; break;
720              case tsip_event_code_e.DIALOG_GLOBAL_ERROR: s_type = 'global_error'; break;
721              case tsip_event_code_e.DIALOG_MESSAGE_ERROR: s_type = 'message_error'; break;
722              case tsip_event_code_e.DIALOG_WEBRTC_ERROR: s_type = 'webrtc_error'; break;
723              case tsip_event_code_e.DIALOG_REQUEST_INCOMING: s_type = 'i_request'; break;
724              case tsip_event_code_e.DIALOG_REQUEST_OUTGOING: s_type = 'o_request'; break;
725              case tsip_event_code_e.DIALOG_REQUEST_CANCELLED: s_type = 'cancelled_request'; break;
726              case tsip_event_code_e.DIALOG_REQUEST_SENT: s_type = 'sent_request'; break;
727              case tsip_event_code_e.DIALOG_MEDIA_ADDED: s_type = 'media_added'; break;
728              case tsip_event_code_e.DIALOG_MEDIA_REMOVED: s_type = 'media_removed'; break;
729              case tsip_event_code_e.DIALOG_CONNECTING: s_type = 'connecting'; break;
730              case tsip_event_code_e.DIALOG_CONNECTED: s_type = 'connected'; break;
731              case tsip_event_code_e.DIALOG_TERMINATING: s_type = 'terminating'; break;
732              case tsip_event_code_e.DIALOG_TERMINATED: 
733                 {
734                     s_type = 'terminated'; 
735                     e.o_session.o_stack.oStack.ao_sessions[i_session_id] = undefined; 
736                     break;
737                 }
738              default: break;
739          }
740 
741          if (s_type) {
742              oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) });
743          }
744      }
745 
746      // listen for MESSAGE events
747      this.o_stack.on_event_message = function (e) {
748          var s_type = null;
749          var i_session_id = e.o_session.i_id;
750          var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id];
751 
752          switch (e.e_message_type) {
753              case tsip_event_message_type_e.I_MESSAGE: s_type = 'i_new_message'; break;
754              case tsip_event_message_type_e.AO_MESSAGE: s_type = 'i_ao_request'; break;
755          }
756 
757          if (s_type) {
758              // 'i_new_call' is stack-level event
759              if (s_type == 'i_new_message') {
760                 var oNewEvent = new SIPml.Stack.Event(s_type, e);
761                 oNewEvent.newSession = new SIPml.Session.Message(e.o_session);
762                 e.o_session.o_stack.oStack.ao_sessions[i_session_id] = oNewEvent.newSession; // save session
763                 e.o_session.o_stack.oStack.dispatchEvent({ s_type: s_type, o_value:  oNewEvent});
764              }
765              else {
766                  if(oSession){
767                     oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) });
768                  }
769                  else{
770                     tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type);
771                  }
772              }
773          }
774      };
775 
776       // listen for PUBLISH events
777      this.o_stack.on_event_publish = function (e) {
778          var s_type = null;
779          var i_session_id = e.o_session.i_id;
780          var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id];
781          if(!oSession){
782             tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type);
783             return;
784          }
785 
786          switch(e.e_publish_type){
787             case tsip_event_publish_type_e.I_PUBLISH: break;
788             case tsip_event_publish_type_e.I_UNPUBLISH: break;
789             case tsip_event_publish_type_e.AO_PUBLISH: 
790             case tsip_event_publish_type_e.AO_UNPUBLISH:
791                 {
792                     s_type = 'i_ao_request'; 
793                     break;
794                 }
795          }
796          if(s_type){
797             oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) });
798          }
799      }
800 
801      // listen for SUBSCRIBE events
802      this.o_stack.on_event_subscribe = function (e) {
803          var s_type = null;
804          var i_session_id = e.o_session.i_id;
805          var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id];
806          if(!oSession){
807             tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type);
808             return;
809          }
810 
811          switch(e.e_subscribe_type){
812             case tsip_event_subscribe_type_e.I_SUBSCRIBE: break;
813             case tsip_event_subscribe_type_e.I_UNSUBSRIBE: break;
814             case tsip_event_subscribe_type_e.AO_SUBSCRIBE: 
815             case tsip_event_subscribe_type_e.AO_UNSUBSCRIBE:
816             case tsip_event_subscribe_type_e.AO_NOTIFY:
817                 {
818                     s_type = 'i_ao_request';
819                     break;
820                 }
821             case tsip_event_subscribe_type_e.I_NOTIFY:
822                 {
823                     s_type = 'i_notify';
824                     break;
825                 }
826          }
827          if(s_type){
828             oSession.dispatchEvent({ s_type: s_type, o_value: new SIPml.Session.Event(oSession, s_type, e) });
829          }
830      }
831 
832 
833      // listen for INVITE events
834      this.o_stack.on_event_invite = function (e) {
835          var s_type = null;
836          var i_session_id = e.o_session.i_id;
837          var oSession = e.o_session.o_stack.oStack.ao_sessions[i_session_id];
838          if (!oSession) {
839              switch (e.e_invite_type) {
840                 case tsip_event_invite_type_e.I_NEW_CALL:
841                 case tsip_event_invite_type_e.M_STREAM_LOCAL_REQUESTED:
842                 case tsip_event_invite_type_e.M_STREAM_LOCAL_ACCEPTED:
843                 case tsip_event_invite_type_e.M_STREAM_LOCAL_REFUSED:
844                     break;
845 
846                 case tsip_event_invite_type_e.M_STREAM_LOCAL_ADDED:
847                 case tsip_event_invite_type_e.M_STREAM_REMOTE_ADDED:
848                 case tsip_event_invite_type_e.M_STREAM_LOCAL_REMOVED:
849                 case tsip_event_invite_type_e.M_STREAM_REMOTE_REMOVED:
850                 case tsip_event_invite_type_e.I_AO_REQUEST:
851                     tsk_utils_log_info('Not notifying to session with id = ' + i_session_id + ' for event = ' + e.e_invite_type);
852                     return;
853 
854                  default:
855                     tsk_utils_log_warn('Cannot find session with id = ' + i_session_id + ' and event = ' + e.e_invite_type);
856                     return;
857              }
858          }
859 
860          
861 
862          var _setStream = function(o_view, o_stream, o_url, b_audio){
863             if(o_stream){
864                 if(!b_audio && o_stream.videoTracks.length > 0){
865                     if (window.HTMLVideoElement && o_view instanceof window.HTMLVideoElement){
866                         if((o_view.src = o_url)){
867                             o_view.play();
868                         }
869                     }
870                     return true;
871                 }
872                 if(b_audio && o_stream.audioTracks.length > 0){
873                     if (window.HTMLAudioElement && o_view instanceof window.HTMLAudioElement){
874                         if((o_view.src = o_url)){
875                             o_view.play();
876                         }
877                     }
878                     return true;
879                 }
880             }
881          }
882 
883          var attachStream = function(bLocal){
884             var o_stream = bLocal ? e.o_session.get_stream_local() : e.o_session.get_stream_remote();
885             var o_url = bLocal ? e.o_session.get_url_local() : e.o_session.get_url_remote();
886             if(_setStream((bLocal ? oSession.videoLocal : oSession.videoRemote), o_stream, o_url, false)){
887                 dispatchEvent(bLocal ? 'm_stream_video_local_added' : 'm_stream_video_remote_added');
888             }
889             if(_setStream((bLocal ? oSession.audioLocal : oSession.audioRemote), o_stream, o_url, true)){
890                 dispatchEvent(bLocal ? 'm_stream_audio_local_added' : 'm_stream_audio_remote_added');
891             }
892          }
893          var deattachStream = function(bLocal){
894             var o_stream = bLocal ? e.o_session.get_stream_local() : e.o_session.get_stream_remote();
895             if(_setStream((bLocal ? oSession.videoLocal : oSession.videoRemote), o_stream, null, false)){
896                 dispatchEvent(bLocal ? 'm_stream_video_local_removed' : 'm_stream_video_remote_removed');
897             }
898             if(_setStream((bLocal ? oSession.audioLocal : oSession.audioRemote), o_stream, null, true)){
899                 dispatchEvent(bLocal ? 'm_stream_audio_local_removed' : 'm_stream_audio_remote_removed');
900             }
901          }
902 
903         var dispatchEvent = function (s_event_type) {
904             if (s_event_type) {
905                 // 'i_new_call', 'm_permission_requested', 'm_permission_accepted' and 'm_permission_refused' are stack-level event
906                 switch (s_event_type) {
907                     case 'i_new_call':
908                     case 'm_permission_requested':
909                     case 'm_permission_accepted':
910                     case 'm_permission_refused':
911                         {
912                             var oNewEvent = new SIPml.Stack.Event(s_event_type, e);
913                             if(s_event_type == 'i_new_call'){
914                                 oNewEvent.newSession = new SIPml.Session.Call(e.o_session);
915                                 e.o_session.o_stack.oStack.ao_sessions[i_session_id] = oNewEvent.newSession; // save session
916                             }
917                             e.o_session.o_stack.oStack.dispatchEvent({ s_type: s_event_type, o_value: oNewEvent });
918                             break;
919                         }
920                     default:
921                         {
922                             oSession.dispatchEvent({ s_type: s_event_type, o_value: new SIPml.Session.Event(oSession, s_event_type, e) });
923                             break;
924                         }
925                 }
926             }
927         }
928 
929          switch (e.e_invite_type) {
930              case tsip_event_invite_type_e.I_NEW_CALL: s_type = 'i_new_call'; break;
931              case tsip_event_invite_type_e.I_ECT_NEW_CALL: s_type = 'i_ect_new_call'; break;
932              case tsip_event_invite_type_e.I_AO_REQUEST: s_type = 'i_ao_request'; break;
933              case tsip_event_invite_type_e.M_EARLY_MEDIA: s_type = 'm_early_media'; break;
934              case tsip_event_invite_type_e.M_STREAM_LOCAL_REQUESTED: s_type = 'm_permission_requested'; break;
935              case tsip_event_invite_type_e.M_STREAM_LOCAL_ACCEPTED: s_type = 'm_permission_accepted'; break;
936              case tsip_event_invite_type_e.M_STREAM_LOCAL_REFUSED: s_type = 'm_permission_refused'; break;
937              case tsip_event_invite_type_e.M_STREAM_LOCAL_ADDED:
938                  {
939                    return attachStream(true);
940                  }
941              case tsip_event_invite_type_e.M_STREAM_LOCAL_REMOVED:
942                  { 
943                     return deattachStream(true);
944                  }
945              case tsip_event_invite_type_e.M_STREAM_REMOTE_ADDED:
946                  { 
947                     return attachStream(false);
948                  }
949              case tsip_event_invite_type_e.M_STREAM_REMOTE_REMOVED:
950                 {
951                     return deattachStream(false);
952                 }
953              case tsip_event_invite_type_e.M_LOCAL_HOLD_OK: s_type = 'm_local_hold_ok'; break;
954              case tsip_event_invite_type_e.M_LOCAL_HOLD_NOK: s_type = 'm_local_hold_nok'; break;
955              case tsip_event_invite_type_e.M_LOCAL_RESUME_OK: s_type = 'm_local_resume_ok'; break;
956              case tsip_event_invite_type_e.M_LOCAL_RESUME_NOK: s_type = 'm_local_resume_nok'; break;
957              case tsip_event_invite_type_e.M_REMOTE_HOLD: s_type = 'm_remote_hold'; break;
958              case tsip_event_invite_type_e.M_REMOTE_RESUME: s_type = 'm_remote_resume'; break;
959              case tsip_event_invite_type_e.O_ECT_TRYING: s_type = 'o_ect_trying'; break;
960              case tsip_event_invite_type_e.O_ECT_ACCEPTED: s_type = 'o_ect_accepted'; break;
961              case tsip_event_invite_type_e.O_ECT_COMPLETED: s_type = 'o_ect_completed'; break;
962              case tsip_event_invite_type_e.I_ECT_COMPLETED: s_type = 'i_ect_completed'; break;
963              case tsip_event_invite_type_e.O_ECT_FAILED: s_type = 'o_ect_failed'; break;
964              case tsip_event_invite_type_e.I_ECT_FAILED: s_type = 'i_ect_failed'; break;
965              case tsip_event_invite_type_e.O_ECT_NOTIFY: s_type = 'o_ect_notify'; break;
966              case tsip_event_invite_type_e.I_ECT_NOTIFY: s_type = 'i_ect_notify'; break;
967              case tsip_event_invite_type_e.I_ECT_REQUESTED: s_type = 'i_ect_requested'; break;
968              default: break;
969          }
970 
971          // dispatch event
972          dispatchEvent(s_type);
973      }
974 }
975 
976 SIPml.Stack.prototype = Object.create(SIPml.EventTarget.prototype);
977 SIPml.Stack.prototype.ao_sessions = [];
978 
979 /**
980 Updates configuration values.
981 @param {SIPml.Stack.Configuration} configuration Configuration object value.
982 @returns {Integer} 0 if successful; otherwise nonzero
983 @example
984 // add two new headers and change the <i>proxy_url</i>
985 o_stack.setConfiguration({
986     proxy_url: 'ws://192.168.0.10:5060',
987     sip_headers: [
988             {name: 'User-Agent', value: 'IM-client/OMA1.0 sipML5-v1.0.89.0'}, 
989             {name: 'Organization', value: 'Doubango Telecom'}
990         ]
991     }
992 );
993 */
994 SIPml.Stack.prototype.setConfiguration = function (o_conf) {
995     if (o_conf.realm && !tsk_string_is_string(o_conf.realm)) {
996         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.realm + "' not a valid type for realm. String is expected");
997     }
998     if (o_conf.impi && !tsk_string_is_string(o_conf.impi)) {
999         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.impi + "' not a valid type for impi. String is expected");
1000     }
1001     if (o_conf.impu && !tsk_string_is_string(o_conf.impu)) {
1002         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.impu + "' not a valid type for impu. String is expected");
1003     }
1004     if (o_conf.password && !tsk_string_is_string(o_conf.password)) {
1005         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.password + "' not a valid type for password. String is expected");
1006     }
1007     if (o_conf.display_name && !tsk_string_is_string(o_conf.display_name)) {
1008         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.display_name + "' not a valid type for display_name. String is expected");
1009     }
1010     if (o_conf.websocket_proxy_url && !tsk_string_is_string(o_conf.websocket_proxy_url)) {
1011         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.websocket_proxy_url + "' not a valid type for websocket_proxy_url. String is expected");
1012     }
1013     if (o_conf.outbound_proxy_url && !tsk_string_is_string(o_conf.outbound_proxy_url)) {
1014         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.outbound_proxy_url + "' not a valid type for outbound_proxy_url. String is expected");
1015     }
1016     if (o_conf.sip_headers && typeof o_conf.sip_headers != "Array" && !(o_conf.sip_headers instanceof Array)) {
1017         throw new Error("ERR_INVALID_PARAMETER_TYPE: '" + typeof o_conf.sip_headers + "' not a valid type for sip_headers. Array is expected");
1018     }
1019 
1020     // event-listener: must be first to be defined as other configs could raise events
1021     if(o_conf.events_listener){
1022         this.addEventListener(o_conf.events_listener.events, o_conf.events_listener.listener);
1023     }
1024 
1025     var b_rtcweb_breaker_enabled = !!o_conf.enable_rtcweb_breaker;
1026     var b_click2call_enabled = !!o_conf.enable_click2call;
1027     var b_early_ims = (o_conf.enable_early_ims == undefined) ? true : !!o_conf.enable_early_ims; // default value is true
1028     var b_enable_media_stream_cache = !!o_conf.enable_media_stream_cache;
1029     var o_bandwidth = o_conf.bandwidth ? o_conf.bandwidth : { audio:undefined, video:undefined };
1030     var o_video_size = o_conf.video_size ? o_conf.video_size : { minWidth:undefined, minHeight:undefined, maxWidth:undefined, maxHeight:undefined };
1031     var o_stack = this.o_stack;
1032     tsk_utils_log_info("s_websocket_server_url=" + (o_conf.websocket_proxy_url || "(null)"));
1033     tsk_utils_log_info("s_sip_outboundproxy_url=" + (o_conf.outbound_proxy_url || "(null)"));
1034     tsk_utils_log_info("b_rtcweb_breaker_enabled=" + (b_rtcweb_breaker_enabled ? "yes" : "no"));
1035     tsk_utils_log_info("b_click2call_enabled=" + (b_click2call_enabled ? "yes" : "no"));
1036     tsk_utils_log_info("b_early_ims=" + (b_early_ims ? "yes" : "no"));
1037     tsk_utils_log_info("b_enable_media_stream_cache=" + (b_enable_media_stream_cache ? "yes" : "no"));
1038     tsk_utils_log_info("o_bandwidth=" + JSON.stringify(o_bandwidth));
1039     tsk_utils_log_info("o_video_size=" + JSON.stringify(o_video_size));
1040 
1041     o_stack.set(tsip_stack.prototype.SetPassword(o_conf.password),
1042                      tsip_stack.prototype.SetDisplayName(o_conf.display_name),
1043                      tsip_stack.prototype.SetProxyOutBoundUrl(o_conf.outbound_proxy_url),
1044                      tsip_stack.prototype.SetRTCWebBreakerEnabled(b_rtcweb_breaker_enabled),
1045                      tsip_stack.prototype.SetClick2CallEnabled(b_click2call_enabled),
1046                      tsip_stack.prototype.SetSecureTransportEnabled(b_rtcweb_breaker_enabled), // always use secure transport when RTCWebBreaker
1047                      tsip_stack.prototype.SetEarlyIMSEnabled(b_early_ims), // should be 'true' unless you're using a real IMS network
1048                      tsip_stack.prototype.SetWebsocketServerUrl(o_conf.websocket_proxy_url),
1049                      tsip_stack.prototype.SetIceServers(o_conf.ice_servers),
1050                      tsip_stack.prototype.SetMediaStreamCacheEnabled(b_enable_media_stream_cache),
1051                      tsip_stack.prototype.SetBandwidth(o_bandwidth),
1052                      tsip_stack.prototype.SetVideoSize(o_video_size));
1053 
1054     // add sip headers
1055     if (o_conf.sip_headers) {
1056         o_conf.sip_headers.forEach(function (o_header) {
1057             if (o_header && !tsk_string_is_null_or_empty(o_header.name) && (!o_header.value || tsk_string_is_string(o_header.value))) {
1058                 o_stack.set(
1059                     tsip_stack.prototype.SetHeader(o_header.name, o_header.value)
1060                 );
1061             }
1062         });
1063     }
1064 
1065     return 0;
1066 }
1067 
1068 
1069 /**
1070 Starts the SIP stack and connect the network transport to the WebSocket server without sending any SIP request. 
1071 This function must be be called before any attempt to make or receive calls/messages. This function is asynchronous which means that the stack will not be immediately started after the call.
1072 Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events.
1073 @returns {Integer} 0 if successful; otherwise nonzero
1074 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font>
1075 */
1076 SIPml.Stack.prototype.start = function () {
1077     return this.o_stack.start();
1078 }
1079 
1080 /**
1081 Stops the SIP stack and disconnect the network transport from the WebSocket server. This function will also hangup all calls and unregister the user from the SIP server.
1082 Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events.
1083 @param {Integer} [timeout] Optional parameter used to defined maximum time in milliseconds to take to stop the stack. 
1084 Default value: 2000 millis
1085 @returns {Integer} 0 if successful; otherwise nonzero
1086 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font>
1087 */
1088 SIPml.Stack.prototype.stop = function (i_timeout) {
1089     return this.o_stack.stop(i_timeout);
1090 }
1091 
1092 /**
1093 Create new SIP session.
1094 @param {String} type Session type. Supported values: <b>'register'</b>, <b>'call-audio'</b>, <b>'call-audiovideo'</b>, <b>'call-video'</b>, <b>'call-screenshare'</b>, <b>'message'</b>, <b>'subscribe'</b> or <b>'publish'</b>.
1095 @param {SIPml.Session.Configuration} [configuration] Anonymous object used to configure the newly created session.
1096 @throws {ERR_INVALID_PARAMETER_VALUE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> <br>
1097 @returns {SIPml.Session} New session if successful; otherwise null.<br> The session type would be <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a> <br>
1098 
1099 @example
1100 var <a href="SIPml.Session.Registration.html">o_registration</a> = this.<a href="#newSession">newSession</a>('register', {
1101             expires: 200,
1102             sip_caps: [
1103                     {name: '+g.oma.sip-im'},
1104                     {name: '+audio'},
1105                     {name: 'language', value: '\"en,fr\"'}
1106             ],
1107             sip_headers: [
1108                     {name: 'What', value: 'Registration', session: false}, 
1109                     {name: 'Organization', value: 'Doubango Telecom', session: true}
1110             ]
1111         });
1112 o_registration.<a href="SIPml.Session.Registration.html#register">register</a>();
1113 
1114 // or
1115 var <a href="SIPml.Session.Call.html">o_audiovideo</a> = this.<a href="#newSession">newSession</a>('call-audiovideo', {
1116             video_local: document.getElementById('video_local'), // <video id="video_local" .../>
1117             video_remote: document.getElementById('video_remote'), // <video id="video_remote" .../>
1118             audio_remote: document.getElementById('audio_remote'), // <audio id="audio_remote" .../>
1119             sip_caps: [
1120                     {name: '+g.oma.sip-im'},
1121                     {name: '+sip.ice'},
1122                     {name: 'language', value: '\"en,fr\"'}
1123             ],
1124             sip_headers: [
1125                     {name: 'What', value: 'Audio/Video call', session: false}, 
1126                     {name: 'Organization', value: 'Doubango Telecom', session: false}
1127             ]
1128         });
1129 o_audiovideo.<a href="SIPml.Session.Call.html#call">call</a>('alice'); // call alice
1130 */
1131 SIPml.Stack.prototype.newSession = function (s_type, o_conf) {
1132     var o_session;
1133     var cls;
1134     if (s_type == 'register') {
1135         o_session = new tsip_session_register(this.o_stack);
1136         cls = SIPml.Session.Registration;
1137     }
1138     else if (s_type == 'message') {
1139         o_session = new tsip_session_message(this.o_stack);
1140         cls = SIPml.Session.Message;
1141     }
1142     else if (s_type == 'publish') {
1143         o_session = new tsip_session_publish(this.o_stack);
1144         cls = SIPml.Session.Publish;
1145     }
1146     else if (s_type == 'subscribe') {
1147         o_session = new tsip_session_subscribe(this.o_stack);
1148         cls = SIPml.Session.Subscribe;
1149     }
1150     else if (s_type == 'call-audio' || s_type == 'call-audiovideo' || s_type == 'call-video' || s_type == 'call-screenshare') {
1151         o_session = new tsip_session_invite(this.o_stack);
1152         o_session.s_type = s_type;
1153         cls = SIPml.Session.Call;
1154     }
1155     else {
1156         throw new Error("ERR_INVALID_PARAMETER_VALUE: '" + s_type + "' not valid as session type");
1157     }
1158 
1159     o_session.b_local = true; // locally created session
1160     var oSession = new cls(o_session, o_conf);
1161     this.ao_sessions[oSession.getId()] = oSession;
1162     return oSession;
1163 }
1164 
1165 // ================================== SIPml.Stack.Event ==========================================
1166 
1167 /** 
1168 SIP Stack event object. You should never create an instance of this object.
1169 @constructor
1170 @extends SIPml.Event
1171 @param {String} type The event type/identifier.
1172 @param {tsip_event} [event] The wrapped event object.
1173 @property {String} type The event type or identifier (e.g. <i>'started'</i> or <i>'i_new_call'</i>). Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Stack">this link</a> for more information on all supported events.
1174 @property {String} description User-friendly description in english (e.g. <i>'Stack started'</i> or <i>'<b>I</b>ncoming <b>new</b> <b>call</b>'</i>).
1175 @property {SIPml.Session} [newSession] Optional session object only defined when the event is about a new session creation (e.g. <i>'i_new_call'</i>).
1176 The session type would be <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>
1177 */
1178 SIPml.Stack.Event = function (s_type, o_event) {
1179     SIPml.Event.call(this, s_type, o_event);
1180 }
1181 
1182 SIPml.Stack.Event.prototype = Object.create(SIPml.Event.prototype);
1183 
1184 // ================================== SIPml.Session ==========================================
1185 
1186 /**
1187 Anonymous SIP Session configuration object.
1188 @namespace SIPml.Session.Configuration
1189 @name SIPml.Session.Configuration
1190 @property {Integer} [expires] Session timeout in seconds. 
1191 @property {HTMLVideoElement} [video_local] <a href="https://developer.mozilla.org/en-US/docs/DOM/HTMLVideoElement">HTMLVideoElement<a> where to display the local video preview. This propety should be only used for <a href="SIPml.Session.Call.html">video sessions</a>.
1192 @property {HTMLVideoElement} [video_remote] <a href="https://developer.mozilla.org/en-US/docs/DOM/HTMLVideoElement">HTMLVideoElement<a> where to display the remote video stream. This propety should be only used for <a href="SIPml.Session.Call.html">video sessions</a>.
1193 @property {HTMLAudioElement} [audio_remote] <a href="https://developer.mozilla.org/en-US/docs/DOM/HTMLAudioElement">HTMLAudioElement<a> used to playback the remote audio stream. This propety should be only used for <a href="SIPml.Session.Call.html">audio sessions</a>.
1194 @property {Array} [sip_caps] <i>{name,value}</i> pairs defining the SIP capabilities associated to this session. The capabilities are added to the Contact header. Please refer to <a href="http://tools.ietf.org/html/rfc3840">rfc3840</a> and <a href="http://tools.ietf.org/html/rfc3841">rfc3841</a> for more information.
1195 @property {String} [from] Set the source uri string to be used in the <i>From</i> header (available since API version 1.2.170).
1196 @property {Object} [bandwidth] Defines the maximum audio and video bandwidth to use. This will change the outhoing SDP to include a "b:AS=" attribute. Use <i>0</i> to let the browser negotiates the right value using RTCP-REMB and congestion control. A default value for all sessions could be defined at stack level.<br />
1197 <i>Available since version 1.3.203</i>. <br />
1198 Example: <i>{ audio:64, video:512 }</i>
1199 @property {Object} [video_size] Defines the maximum and minimum video size to be used. All values are optional. The browser will try to find the best video size between <i>max</i> and <i>min</i> based on the camera capabilities. A default value for all sessions could be defined at stack level.<br />
1200 <i>Available since version 1.3.203</i>. <br />
1201 Example: <i>{ minWidth:640, minHeight:480, maxWidth:1920, maxHeight:1080 }</i>
1202 @property {Array} [sip_headers] <i>{name,value,session}</i> trios defining the SIP headers associated to this session. <i>session</i> is a boolean defining whether the header have to be added to all outgoing request or not (initial only).
1203 @example
1204 var configuration = 
1205 {
1206     expires: 200,
1207     audio_remote: document.getElementById('audio_remote'), // <audio id="audio_remote" .../>
1208     video_local: document.getElementById('video_local'),  // <video id="video_local" .../>
1209     video_remote: document.getElementById('video_remote'),  // <video id="video_remote" .../>
1210     sip_caps: [
1211                     {name: '+g.oma.sip-im'},
1212                     {name: '+sip.ice'},
1213                     {name: 'language', value: '\"en,fr\"'}
1214             ],
1215     sip_headers: [
1216                     {name: 'What', value: 'Audio/Video call', session: false}, 
1217                     {name: 'Organization', value: 'Doubango Telecom', session: false}
1218             ]
1219 }
1220 */
1221 
1222 /** 
1223 Base (abstract) SIP session. You should never create an instance of this class by yourself. You have to use <a href="SIPml.Stack.html#newSession"> newSession()</a> to create a new instance.
1224 This is a base class for <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> and <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>.
1225 @constructor
1226 @extends SIPml.EventTarget
1227 @param {tsip_session_t} session Private wrapped session object.
1228 @param {SIPml.Session.Configuration} [configuration] Optional configuration object.
1229 @throws {ERR_INVALID_PARAMETER_VALUE|ERR_INVALID_PARAMETER_TYPE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_INVALID_PARAMETER_TYPE</font>
1230 */
1231 SIPml.Session = function (o_session, o_conf) {
1232      SIPml.EventTarget.call(this);
1233     /*
1234         - o_configuration: []
1235         - o_session: tsip_session_xxx
1236     */
1237     if (!o_session) {
1238         throw new Error("ERR_INVALID_PARAMETER_VALUE: Invalid session value");
1239     }
1240 
1241     this.o_session = o_session;
1242     this.setConfiguration(o_conf);
1243 }
1244 
1245 SIPml.Session.prototype = Object.create(SIPml.EventTarget.prototype);
1246 SIPml.Session.prototype.o_session = null;
1247 SIPml.Session.prototype.o_session = null;
1248 SIPml.Session.prototype.o_configuration = null;
1249 
1250 /**
1251 Gets the session unique identifier.
1252 @returns {Integer} Read-only session unique identifier.
1253 */
1254 SIPml.Session.prototype.getId = function(){
1255     return this.o_session.get_id();
1256 }
1257 
1258 /**
1259 Updates or sets the session configuration.
1260 @param {SIPml.Session.Configuration} configuration
1261 */
1262 SIPml.Session.prototype.setConfiguration = function(o_conf){
1263     if(!o_conf){
1264         return;
1265     }
1266 
1267     var o_session = this.o_session;
1268 
1269     // event-listener: must be first to be defined as other configs could raise events
1270     if(o_conf.events_listener){
1271         this.addEventListener(o_conf.events_listener.events, o_conf.events_listener.listener);
1272     }
1273 
1274     if(this instanceof SIPml.Session.Call){
1275         // Bandwidth and video size
1276         o_session.set(
1277                     tsip_session.prototype.SetBandwidth(o_conf.bandwidth ? o_conf.bandwidth : { audio:undefined, video:undefined }),
1278                     tsip_session.prototype.SetVideoSize(o_conf.video_size ? o_conf.video_size : { minWidth:undefined, minHeight:undefined, maxWidth:undefined, maxHeight:undefined })
1279                 );
1280 
1281         this.videoLocal = o_conf.video_local;
1282         this.videoRemote = o_conf.video_remote;
1283         this.audioRemote = o_conf.audio_remote;
1284         this.audioLocal = o_conf.audio_local;
1285         
1286          var _addStream = function(o_view, o_stream, o_url, b_audio){
1287             if(o_stream){
1288                 if(!b_audio && o_stream.videoTracks.length > 0){
1289                     if (window.HTMLVideoElement && o_view instanceof window.HTMLVideoElement){
1290                         if(o_view.src == o_url) return false; // unchanged
1291                         else if(o_view.src = o_url) o_view.play();
1292                     }
1293                     return true;
1294                 }
1295                 if(b_audio && o_stream.audioTracks.length > 0){
1296                     if (window.HTMLAudioElement && o_view instanceof window.HTMLAudioElement){
1297                         if(o_view.src == o_url) return false; // unchanged
1298                         else if(o_view.src = o_url) o_view.play();
1299                     }
1300                     return true;
1301                 }
1302             }
1303          }
1304 
1305         if(_addStream(this.videoLocal, o_session.get_stream_local(), o_session.get_url_local(), false)){
1306             this.dispatchEvent({ s_type: 'm_stream_video_local_added', o_value: new SIPml.Session.Event(this, 'm_stream_video_local_added') });
1307         }
1308         if(_addStream(this.videoRemote, o_session.get_stream_remote(), o_session.get_url_remote(), false)){
1309             this.dispatchEvent({ s_type: 'm_stream_video_remote_added', o_value: new SIPml.Session.Event(this, 'm_stream_video_remote_added') });
1310         }
1311         if(_addStream(this.audioLocal, o_session.get_stream_local(), o_session.get_url_local(), true)){
1312             this.dispatchEvent({ s_type: 'm_stream_audio_local_added', o_value: new SIPml.Session.Event(this, 'm_stream_audio_local_added') });
1313         }
1314         if(_addStream(this.audioRemote, o_session.get_stream_remote(), o_session.get_url_remote(), true)){
1315             this.dispatchEvent({ s_type: 'm_stream_audio_remote_added', o_value: new SIPml.Session.Event(this, 'm_stream_audio_remote_added') });
1316         }
1317     }
1318 
1319     
1320     // headers
1321     if (o_conf.sip_headers) {
1322         o_conf.sip_headers.forEach(function (o_header) {
1323             if (o_header && !tsk_string_is_null_or_empty(o_header.name) && (!o_header.value || tsk_string_is_string(o_header.value))) {
1324                 o_session.set(
1325                     tsip_session.prototype.SetHeader(o_header.name, o_header.value)
1326                 );
1327             }
1328         });
1329     }
1330     // caps
1331     if (o_conf.sip_caps) {
1332         o_conf.sip_caps.forEach(function (o_cap) {
1333             if (o_cap && !tsk_string_is_null_or_empty(o_cap.name) && (!o_cap.value || tsk_string_is_string(o_cap.value))) {
1334                 o_session.set(
1335                     tsip_session.prototype.SetCaps(o_cap.name, o_cap.value)
1336                 );
1337             }
1338         });
1339     }
1340     // expires
1341     if (o_conf.expires) {
1342         o_session.set(tsip_session.prototype.SetExpires(o_conf.expires));
1343     }
1344     // from
1345     if (o_conf.from) {
1346         o_session.set(tsip_session.prototype.SetFromStr(o_conf.from));
1347     }
1348 }
1349 
1350 /**
1351 Gets the remote party SIP Uri (e.g. sip:john.doe@example.com). This Uri could be used a match an incoming call to a contact from your address book.
1352 @returns {String} The remote party SIP Uri.
1353 @see <a href="#getRemoteFriendlyName">getRemoteFriendlyName</a>
1354 */
1355 SIPml.Session.prototype.getRemoteUri = function(){
1356     return (this.o_session.b_local ? this.o_session.o_uri_to : this.o_session.o_uri_from).toString();
1357 }
1358 
1359 /**
1360 Gets the remote party friendly name (e.g. 'John Doe').
1361 @returns {String} The remote party friendly name.
1362 @see <a href="#getRemoteUri">getRemoteUri</a>
1363 */
1364 SIPml.Session.prototype.getRemoteFriendlyName = function(){
1365     var o_uri = this.o_session.b_local ? this.o_session.o_uri_to : this.o_session.o_uri_from;
1366     return o_uri.s_display_name ? o_uri.s_display_name : o_uri.s_user_name;
1367 }
1368 
1369 /**
1370 Rejects an incoming SIP MESSAGE (SMS-like) or audio/video call.
1371 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1372 @returns {Integer} 0 if successful; otherwise nonzero
1373 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1374 */
1375 SIPml.Session.prototype.reject = function (o_conf) {
1376     this.setConfiguration(o_conf);
1377     return this.o_session.reject();
1378 }
1379 
1380 /**
1381 Accepts an incoming SIP MESSAGE (SMS-like) or audio/video call.
1382 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1383 @returns {Integer} 0 if successful; otherwise nonzero
1384 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1385 */
1386 SIPml.Session.prototype.accept = function (o_conf) {
1387     this.setConfiguration(o_conf);
1388     return this.o_session.accept();
1389 }
1390 
1391 
1392 
1393 // ================================== SIPml.Session.Event ==================================
1394 
1395 
1396 /** 
1397 SIP session event object. You should never create an instance of this class by yourself.
1398 @constructor
1399 @extends SIPml.Event
1400 @param {SIPml.Session} [session] The session type would be <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>.
1401 @param {String} type The event type or identifier. Please check <a href="SIPml.EventTarget.html#SIPml.EventTarget.Session">this link</a> for more information about all supported session event types.
1402 @param {tsip_event} [event] Private wrapped session object.
1403 @property {SIPml.Session} session Session associated to this event. Would be <a href="SIPml.Session.Registration.html">SIPml.Session.Registration</a>, <a href="SIPml.Session.Call.html">SIPml.Session.Call</a> or <a href="SIPml.Session.Message.html">SIPml.Session.Message</a>.
1404 */
1405 SIPml.Session.Event = function (o_session, s_type, o_event) {
1406     SIPml.Event.call(this, s_type, o_event);
1407     this.session = o_session;
1408 }
1409 
1410 SIPml.Session.Event.prototype = Object.create(SIPml.Event.prototype);
1411 
1412 
1413 /**
1414 Gets the name of destination for the current call transfer. 
1415 @returns {String} The name of destination for the current call transfer (e.g. 'John Doe').
1416 */
1417 SIPml.Session.Event.prototype.getTransferDestinationFriendlyName = function () {
1418     var o_message = this.o_event ? this.o_event.get_message() : null;
1419     if (o_message) {
1420         var o_hdr_Refer_To = o_message.get_header(tsip_header_type_e.Refer_To);
1421         if(o_hdr_Refer_To && o_hdr_Refer_To.o_uri){
1422             return (o_hdr_Refer_To.s_display_name ? o_hdr_Refer_To.s_display_name : o_hdr_Refer_To.o_uri.s_user_name);
1423         }
1424     }
1425     return null;
1426 }
1427 
1428 // ================================== SIPml.Registration ==================================
1429 
1430 /**
1431 SIP session registration class. You should never create an instance of this class by yourself.
1432 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new registration session.
1433 @constructor
1434 @extends SIPml.Session
1435 @param {tsip_session} session Private wrapped session object
1436 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1437 */
1438 SIPml.Session.Registration = function (o_session, o_configuration) {
1439     SIPml.Session.call(this, o_session, o_configuration);
1440 }
1441 
1442 SIPml.Session.Registration.prototype = Object.create(SIPml.Session.prototype);
1443 
1444 /**
1445 Sends SIP REGISTER request to login the user. Refreshing requests will be automatically done based on the expiration time.
1446 @param {SIPml.Session.Configuration} [configuration] Optional configuration value.
1447 @example
1448 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('register', {
1449                             expires: 200,
1450                             events_listener: { events: '*', listener: onSipEventSession },
1451                             sip_caps: [
1452                                         { name: '+g.oma.sip-im', value: null },
1453                                         { name: '+audio', value: null },
1454                                         { name: 'language', value: '\"en,fr\"' }
1455                                 ]
1456                         });
1457 session.register();
1458 
1459 @see <a href="#unregister">unregister</a>
1460 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font>
1461 */
1462 SIPml.Session.Registration.prototype.register = function (o_conf) {
1463     // FIXME: apply o_configuration
1464     // FIXME: raise error if stack not started
1465     this.setConfiguration(o_conf);
1466     return this.o_session.register();
1467 }
1468 
1469 /**
1470 Sends SIP REGISTER (expires=0) request to logout the user.
1471 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1472 @see <a href="#register">register</a>
1473 @throws {ERR_INVALID_STATE} <font color="red">ERR_INVALID_STATE</font>
1474 */
1475 SIPml.Session.Registration.prototype.unregister = function (o_conf) {
1476     // FIXME: raise error if stack not started
1477     this.setConfiguration(o_conf);
1478     return this.o_session.unregister();
1479 }
1480 
1481 // ================================== SIPml.Call ==========================================
1482 
1483 /** 
1484 SIP audio/video/screenshare call session class. You should never create an instance of this class by yourself.
1485 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new audio/video/screenshare session.
1486 @constructor
1487 @extends SIPml.Session
1488 @param {tsip_session} session Private wrapped session object
1489 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1490 @example
1491 var listenerFunc = function(e){
1492     console.info('session event = ' + e.type);
1493 }
1494 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('call-audiovideo', {
1495             video_local: document.getElementById('video-local'), // <video id="video-local" .../>
1496             video_remote: document.getElementById('video-remote'), // <video id="video-remote" .../>
1497             audio_remote: document.getElementById('audio-remote'), // <audio id="audio-remote" .../>
1498             events_listener: { events: '*', listener: listenerFunc },
1499             sip_caps: [
1500                             { name: '+g.oma.sip-im' },
1501                             { name: '+sip.ice' },
1502                             { name: 'language', value: '\"en,fr\"' }
1503                         ]
1504         });
1505 @throws {ERR_INVALID_PARAMETER_VALUE} <font color="red">ERR_INVALID_PARAMETER_VALUE</font>
1506 */
1507 SIPml.Session.Call = function (o_session, o_conf) {
1508     SIPml.Session.call(this, o_session, o_conf);
1509 
1510     switch (o_session.s_type) {
1511         case 'call-audio': this.mediaType = tmedia_type_e.AUDIO; break;
1512         case 'call-audiovideo': this.mediaType = tmedia_type_e.AUDIO_VIDEO; break;
1513         case 'call-video': this.mediaType = tmedia_type_e.VIDEO; break;
1514         case 'call-screenshare': this.mediaType = tmedia_type_e.SCREEN_SHARE; break;
1515     }
1516 }
1517 
1518 SIPml.Session.Call.prototype = Object.create(SIPml.Session.prototype);
1519 SIPml.Session.Call.prototype.videoLocal = null;
1520 SIPml.Session.Call.prototype.videoRemote = null;
1521 
1522 /**
1523 Makes audio/video call.
1524 @param {String} to Destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000').
1525 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1526 @returns {Integer} 0 if successful; otherwise nonzero
1527 @example
1528 var listenerFunc = function(e){
1529     console.info('session event = ' + e.type);
1530 }
1531 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('call-audiovideo');
1532 session.call('johndoe', {
1533             video_local: document.getElementById('video-local'), // <video id="video-local" .../>
1534             video_remote: document.getElementById('video-remote'), // <video id="video-remote" .../>
1535             audio_remote: document.getElementById('audio-remote'), // <audio id="audio-remote" .../>
1536             events_listener: { events: '*', listener: listenerFunc },
1537             sip_caps: [
1538                             { name: '+g.oma.sip-im' },
1539                             { name: '+sip.ice' },
1540                             { name: 'language', value: '\"en,fr\"' }
1541                         ]
1542         });
1543 
1544 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1545 */
1546 SIPml.Session.Call.prototype.call = function (s_to, o_conf) {
1547     if (tsk_string_is_null_or_empty(s_to)) {
1548         throw new Error("ERR_INVALID_PARAMETER_VALUE: 'to' must not be null");
1549     }
1550     if (!SIPml.haveMediaStream()) {
1551         throw new Error("ERR_NOT_READY: Media engine not ready yet");
1552     }
1553     // set destination
1554     this.o_session.set(tsip_session.prototype.SetToStr(s_to));
1555     // set conf
1556     this.setConfiguration(o_conf);
1557     // make call
1558     return this.o_session.call(this.mediaType);
1559 }
1560 
1561 /**
1562 Terminates the audio/video call.
1563 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1564 @returns {Integer} 0 if successful; otherwise nonzero
1565 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1566 */
1567 SIPml.Session.Call.prototype.hangup = function (o_conf) {
1568     this.setConfiguration(o_conf);
1569     return this.o_session.hangup();
1570 }
1571 
1572 /**
1573 Holds the audio/video call.
1574 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1575 @returns {Integer} 0 if successful; otherwise nonzero
1576 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1577 */
1578 SIPml.Session.Call.prototype.hold = function (o_conf) {
1579     this.setConfiguration(o_conf);
1580     return this.o_session.hold(this.mediaType);
1581 }
1582 
1583 /**
1584 Resumes the audio/video call.
1585 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1586 @returns {Integer} 0 if successful; otherwise nonzero
1587 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1588 */
1589 SIPml.Session.Call.prototype.resume = function (o_conf) {
1590     this.setConfiguration(o_conf);
1591     return this.o_session.resume(this.mediaType);
1592 }
1593 
1594 /**
1595 Sends SIP INFO message.
1596 @param {Object|String} [content] SIP INFO request content.
1597 @param {String} [contentType] Content Type.
1598 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1599 @returns {Integer} 0 if successful; otherwise nonzero
1600 @example
1601 session.info('Device orientation: portrait', 'doubango/device-orientation.xml');
1602 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1603 */
1604 SIPml.Session.Call.prototype.info = function (o_content, s_content_type, o_conf) {
1605     this.setConfiguration(o_conf);
1606     return this.o_session.info(o_content, s_content_type);
1607 }
1608 
1609 /**
1610 Sends a SIP DTMF digit.
1611 @param {Char} [digit] The digit to send.
1612 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1613 @returns {Integer} 0 if successful; otherwise nonzero
1614 @example
1615 session.dtmf('#');
1616 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1617 */
1618 SIPml.Session.Call.prototype.dtmf = function (c_digit, o_conf) {
1619     this.setConfiguration(o_conf);
1620     return this.o_session.dtmf(c_digit);
1621 }
1622 
1623 /**
1624 Transfers the call to a new destination.
1625 @param {String} to Transfer destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000').
1626 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1627 @returns {Integer} 0 if successful; otherwise nonzero
1628 @example
1629 session.transfer('johndoe');
1630 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1631 */
1632 SIPml.Session.Call.prototype.transfer = function (s_to, o_conf) {
1633     this.setConfiguration(o_conf);
1634     return this.o_session.transfer(s_to);
1635 }
1636 
1637 /**
1638 Accepts incoming transfer request.
1639 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1640 @returns {Integer} 0 if successful; otherwise nonzero
1641 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1642 */
1643 SIPml.Session.Call.prototype.acceptTransfer = function (o_conf) {
1644     this.setConfiguration(o_conf);
1645     return this.o_session.transfer_accept();
1646 }
1647 
1648 /**
1649 Rejects incoming transfer request.
1650 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1651 @returns {Integer} 0 if successful; otherwise nonzero
1652 @throws {ERR_NOT_READY} <font color="red">ERR_NOT_READY</font>
1653 */
1654 SIPml.Session.Call.prototype.rejectTransfer = function (o_conf) {
1655     this.setConfiguration(o_conf);
1656     return this.o_session.transfer_reject();
1657 }
1658 
1659 // ================================== SIPml.Session.Message ==========================================
1660 
1661 /** 
1662 SIP MESSAGE (SMS) session class. You should never create an instance of this class by yourself.
1663 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a messaging/IM session.
1664 @constructor
1665 @param {tsip_session} session Private session object.
1666 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1667 @example
1668 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('message');
1669 */
1670 SIPml.Session.Message = function (o_session, o_conf) {
1671     SIPml.Session.call(this, o_session, o_conf);
1672 
1673 }
1674 
1675 SIPml.Session.Message.prototype = Object.create(SIPml.Session.prototype);
1676 
1677 /**
1678 Sends a SIP MESSAGE (SMS-like) request.
1679 @param {String} to Destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000').
1680 @param {Object|String} [content] The message content.
1681 @param {String} [contentType] The content type.
1682 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1683 @returns {Integer} 0 if successful; otherwise nonzero
1684 @example
1685 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('message');
1686 session.send('johndoe', 'Pêche à la moule', 'text/plain;charset=utf8',{
1687     sip_caps: [
1688                                     { name: '+g.oma.sip-im' },
1689                                     { name: '+sip.ice' },
1690                                     { name: 'language', value: '\"en,fr\"' }
1691                             ],
1692     sip_headers: [
1693                             { name: 'What', value: 'Sending SMS' },
1694                             { name: 'My-Organization', value: 'Doubango Telecom' }
1695                     ]
1696 });
1697 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1698 */
1699 SIPml.Session.Message.prototype.send = function (s_to, o_content, s_content_type, o_conf){
1700     if (tsk_string_is_null_or_empty(s_to)) {
1701         throw new Error("ERR_INVALID_PARAMETER_VALUE: 'to' must not be null");
1702     }
1703 
1704     // apply configuration values
1705     this.setConfiguration(o_conf);
1706     // set destination
1707     this.o_session.set(tsip_session.prototype.SetToStr(s_to));
1708     // sends the message
1709     return this.o_session.send(o_content, s_content_type);
1710 }
1711 
1712 
1713 
1714 // ================================== SIPml.Session.Publish ==========================================
1715 
1716 
1717 /** 
1718 SIP PUBLISH (for presence status publication) session class.You should never create an instance of this class by yourself.
1719 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new presence publication session.
1720 @constructor
1721 @extends SIPml.Session
1722 @since version 1.1.0
1723 @param {tsip_session} session Private session object.
1724 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1725 @example
1726 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('publish', {
1727                             expires: 200,
1728                             events_listener: { events: '*', listener: function(e){} },
1729                             sip_headers: [
1730                                           { name: 'Event', value: 'presence' } // very important
1731                                 ],
1732                             sip_caps: [
1733                                         { name: '+g.oma.sip-im', value: null },
1734                                         { name: '+audio', value: null },
1735                                         { name: 'language', value: '\"en,fr\"' }
1736                                 ]
1737                         });
1738 */
1739 SIPml.Session.Publish = function (o_session, o_conf) {
1740     SIPml.Session.call(this, o_session, o_conf);
1741     // set destination to ourself (https://groups.google.com/forum/#!topic/doubango/XKWTQ9TgjPU)
1742     o_session.set(tsip_session.prototype.SetToUri(o_session.get_stack().identity.o_uri_impu));
1743 }
1744 
1745 SIPml.Session.Publish.prototype = Object.create(SIPml.Session.prototype);
1746 
1747 /**
1748 Sends a SIP PUBLISH (for presence status publication) request.
1749 @since version 1.1.0
1750 @param {Object|String} [content] The request content.
1751 @param {String} [contentType] The content type.
1752 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1753 @returns {Integer} 0 if successful; otherwise nonzero
1754 @example
1755 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('publish');
1756 var contentType = 'application/pidf+xml';
1757 var content = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n' +
1758                 '<presence xmlns=\"urn:ietf:params:xml:ns:pidf\"\n' +
1759                     ' xmlns:im=\"urn:ietf:params:xml:ns:pidf:im\"' +
1760              	    ' entity=\"sip:bob@example.com\">\n' +
1761                     '<tuple id=\"s8794\">\n' +
1762                     '<status>\n'+
1763                     '   <basic>open</basic>\n' +
1764                     '   <im:im>away</im:im>\n' +
1765                     '</status>\n' +
1766                     '<contact priority=\"0.8\">tel:+33600000000</contact>\n' +
1767                     '<note  xml:lang=\"fr\">Bonjour de Paris :)</note>\n' +
1768                     '</tuple>\n' +
1769    	            '</presence>';
1770 
1771 session.publish(content, contentType,{
1772     expires: 200,
1773     sip_caps: [
1774                                     { name: '+g.oma.sip-im' },
1775                                     { name: '+sip.ice' },
1776                                     { name: 'language', value: '\"en,fr\"' }
1777                             ],
1778     sip_headers: [
1779                             { name: 'Event', value: 'presence' },
1780                             { name: 'Organization', value: 'Doubango Telecom' }
1781                     ]
1782 });
1783 @returns {Integer} 0 if successful; otherwise nonzero
1784 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1785 @see <a href="#unpublish">unpublish</a>
1786 */
1787 SIPml.Session.Publish.prototype.publish = function (o_content, s_content_type, o_conf){
1788     // apply configuration values
1789     this.setConfiguration(o_conf);
1790     // sends the PUBLISH request
1791     return this.o_session.publish(o_content, s_content_type);
1792 }
1793 
1794 /**
1795 Remove/unpublish presence data from the server.
1796 @since version 1.1.0
1797 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1798 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1799 @see <a href="#publish">publish</a>
1800 */
1801 SIPml.Session.Publish.prototype.unpublish = function (o_conf){
1802     // apply configuration values
1803     this.setConfiguration(o_conf);
1804     // sends the PUBLISH request (expires = 0)
1805     return this.o_session.unpublish();
1806 }
1807 
1808 
1809 
1810 
1811 
1812 // ================================== SIPml.Session.Subscribe ==========================================
1813 
1814 
1815 /** 
1816 SIP SUBSCRIBE (for presence status subscription) session class.You should never create an instance of this class by yourself.
1817 Please use <a href="SIPml.Stack.html#newSession">stack.newSession()</a> function to create a new presence subscription session.
1818 @constructor
1819 @extends SIPml.Session
1820 @since version 1.1.0
1821 @param {tsip_session} session Private session object.
1822 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1823 @example
1824 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('subscribe', {
1825                 expires: 200,
1826                 events_listener: { events: '*', listener: function(e){} },
1827                 sip_headers: [
1828                                 { name: 'Event', value: 'presence' },
1829                                 { name: 'Accept', value: 'application/pidf+xml' }
1830                     ],
1831                 sip_caps: [
1832                             { name: '+g.oma.sip-im', value: null },
1833                             { name: '+audio', value: null },
1834                             { name: 'language', value: '\"en,fr\"' }
1835                     ]
1836             });
1837 */
1838 SIPml.Session.Subscribe = function (o_session, o_conf) {
1839     SIPml.Session.call(this, o_session, o_conf);
1840 
1841 }
1842 
1843 SIPml.Session.Subscribe.prototype = Object.create(SIPml.Session.prototype);
1844 
1845 /**
1846 Sends a SIP SUBSCRIBE (for presence status subscription) request.
1847 @since version 1.1.0
1848 @param {String} to Destination name, uri, phone number or identifier (e.g. 'sip:johndoe@example.com' or 'johndoe' or '+33600000000').
1849 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1850 @returns {Integer} 0 if successful; otherwise nonzero
1851 @example
1852 var onEvent = function(e){
1853     if(e.type == 'i_notify'){
1854         // process incoming NOTIFY request
1855     }
1856 }
1857 var session = <a href="SIPml.Stack.html#newSession">stack.newSession</a>('subscribe', {
1858                 expires: 200,
1859                 events_listener: { events: '*', listener: onEvent },
1860                 sip_headers: [
1861                                 { name: 'Event', value: 'presence' },
1862                                 { name: 'Accept', value: 'application/pidf+xml' }
1863                     ],
1864                 sip_caps: [
1865                             { name: '+g.oma.sip-im', value: null },
1866                             { name: '+audio', value: null },
1867                             { name: 'language', value: '\"en,fr\"' }
1868                     ]
1869             });
1870 session.subscribe('johndoe'); // watch for johndoe's presence status
1871 
1872 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1873 @see <a href="#unsubscribe">unsubscribe</a>
1874 */
1875 SIPml.Session.Subscribe.prototype.subscribe = function (s_to, o_conf){
1876      if (tsk_string_is_null_or_empty(s_to)) {
1877         throw new Error("ERR_INVALID_PARAMETER_VALUE: 'to' must not be null");
1878     }
1879     // set destination
1880     this.o_session.set(tsip_session.prototype.SetToStr(s_to));
1881     // apply configuration values
1882     this.setConfiguration(o_conf);
1883     // sends the PUBLISH request
1884     return this.o_session.subscribe();
1885 }
1886 
1887 /**
1888 Unsubscribe.
1889 @since version 1.1.0
1890 @param {SIPml.Session.Configuration} [configuration] Configuration value.
1891 @returns {Integer} 0 if successful; otherwise nonzero
1892 @throws {ERR_INVALID_PARAMETER_VALUE | ERR_NOT_READY} <font color="red">ERR_INVALID_PARAMETER_VALUE</font> | <font color="red">ERR_NOT_READY</font>
1893 @see <a href="#subscribe">subscribe</a>
1894 */
1895 SIPml.Session.Subscribe.prototype.unsubscribe = function (o_conf){
1896     // apply configuration values
1897     this.setConfiguration(o_conf);
1898     // sends the SUBSCRIBE request (expires = 0)
1899     return this.o_session.unsubscribe();
1900 }
1901