      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